From 7759bd36395b7ed92dadceb8f1fd31beff800a86 Mon Sep 17 00:00:00 2001 From: ptitloup Date: Thu, 24 Mar 2022 14:58:18 +0100 Subject: [PATCH 01/45] add video categories to admin site --- pod/video/admin.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pod/video/admin.py b/pod/video/admin.py index c53c521e2c..9ccae97704 100644 --- a/pod/video/admin.py +++ b/pod/video/admin.py @@ -22,6 +22,7 @@ from .models import ViewCount from .models import VideoToDelete from .models import VideoVersion +from .models import Category from .forms import VideoForm, VideoVersionForm from .forms import ChannelForm @@ -719,6 +720,20 @@ def get_queryset(self, request): return qs +class CategoryAdmin(admin.ModelAdmin): + list_display = ( + "title", + "owner", + "videos_count" + ) + readonly_fields = ("slug",) + # list_filter = ["owner"] + + def videos_count(self, obj): + return len(obj.video.all()) + videos_count.short_description = 'Videos' + + admin.site.register(Channel, ChannelAdmin) admin.site.register(Type, TypeAdmin) admin.site.register(Discipline, DisciplineAdmin) @@ -736,3 +751,4 @@ def get_queryset(self, request): admin.site.register(NoteComments, NoteCommentsAdmin) admin.site.register(VideoToDelete, VideoToDeleteAdmin) admin.site.register(ViewCount, ViewCountAdmin) +admin.site.register(Category, CategoryAdmin) From 6a6c2e4c8b781c7d449f4211d5f45e81515b3781 Mon Sep 17 00:00:00 2001 From: ptitloup Date: Thu, 24 Mar 2022 14:59:03 +0100 Subject: [PATCH 02/45] improve category slug length to prevent too long data --- pod/video/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pod/video/models.py b/pod/video/models.py index a0890c0a53..e9e1ce0b54 100755 --- a/pod/video/models.py +++ b/pod/video/models.py @@ -1992,7 +1992,7 @@ class Category(models.Model): slug = models.SlugField( _("Slug"), unique=True, - max_length=100, + max_length=110, help_text=_( 'Used to access this instance, the "slug" is a short label ' + "containing only letters, numbers, underscore or dash top." From b7ec0a43f036e35b97669c46812ff69b8eb6530f Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 24 Mar 2022 14:00:02 +0000 Subject: [PATCH 03/45] Fixup: format code with Prettier --- pod/main/static/js/main.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pod/main/static/js/main.js b/pod/main/static/js/main.js index 9c3bfdf629..7fffd74a77 100644 --- a/pod/main/static/js/main.js +++ b/pod/main/static/js/main.js @@ -83,9 +83,8 @@ function writeInFrame() { var img = document.getElementById("qrcode"); var imgsrc = "//chart.apis.google.com/chart?cht=qr&chs=200x200&chl=" + link; - if(img.getAttribute('src') === "") img.setAttribute("data-src", imgsrc) + if (img.getAttribute("src") === "") img.setAttribute("data-src", imgsrc); else img.src = imgsrc; - } $(document).on("change", "#autoplay", function () { writeInFrame(); @@ -94,13 +93,13 @@ $(document).on("change", "#loop", function () { writeInFrame(); }); -$(document).on('shown.bs.collapse', '#qrcode', function () { - $('#qrcode').attr("src", $('#qrcode').attr("data-src")); -}) +$(document).on("shown.bs.collapse", "#qrcode", function () { + $("#qrcode").attr("src", $("#qrcode").attr("data-src")); +}); -$(document).on('hidden.bs.collapse', '#qrcode', function () { - $('#qrcode').attr("src", ""); -}) +$(document).on("hidden.bs.collapse", "#qrcode", function () { + $("#qrcode").attr("src", ""); +}); $(document).on("change", "#displaytime", function (e) { if ($("#displaytime").is(":checked")) { From 2831df03654e6399c8a5af326c4f35cb59dae36d Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 24 Mar 2022 14:00:13 +0000 Subject: [PATCH 04/45] Fixup: format code with Black --- pod/video/admin.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pod/video/admin.py b/pod/video/admin.py index 9ccae97704..d66f79503c 100644 --- a/pod/video/admin.py +++ b/pod/video/admin.py @@ -721,17 +721,14 @@ def get_queryset(self, request): class CategoryAdmin(admin.ModelAdmin): - list_display = ( - "title", - "owner", - "videos_count" - ) + list_display = ("title", "owner", "videos_count") readonly_fields = ("slug",) # list_filter = ["owner"] def videos_count(self, obj): return len(obj.video.all()) - videos_count.short_description = 'Videos' + + videos_count.short_description = "Videos" admin.site.register(Channel, ChannelAdmin) From 9d539dfcf7840efafe346c8e279b04490f5e4704 Mon Sep 17 00:00:00 2001 From: secale Date: Thu, 31 Mar 2022 14:38:05 +0200 Subject: [PATCH 05/45] fix indentation on theme --- pod/main/static/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pod/main/static/js/main.js b/pod/main/static/js/main.js index 7fffd74a77..0bd073350f 100644 --- a/pod/main/static/js/main.js +++ b/pod/main/static/js/main.js @@ -202,7 +202,7 @@ var get_list = function ( if (count > 0 && !show_only_parent_themes) { list += get_list( child, - (level += 1), + level + 1, tab_selected, tag_type, li_class, From f4d74e9334dadcdc0033dc2dfb870fdb797f0897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9line=20Didier?= Date: Tue, 12 Apr 2022 10:27:29 +0200 Subject: [PATCH 06/45] Increase AccessGroup code length --- pod/authentication/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pod/authentication/models.py b/pod/authentication/models.py index 5d297be2a3..29254655cc 100644 --- a/pod/authentication/models.py +++ b/pod/authentication/models.py @@ -170,7 +170,7 @@ def create_groupsite_profile(sender, instance, created, **kwargs): class AccessGroup(models.Model): display_name = models.CharField(max_length=128, blank=True, default="") - code_name = models.CharField(max_length=128, unique=True) + code_name = models.CharField(max_length=250, unique=True) sites = models.ManyToManyField(Site) users = select2_fields.ManyToManyField( Owner, From 73916e4b85821ebe1fa1ae87586741279098f64d Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 4 May 2022 17:06:57 +0200 Subject: [PATCH 07/45] remove cron file from git, add echo in create_tenant shell file and update settings to update globals settings from tenant settings --- pod/custom/tenants/check_obsolete_videos.sh | 4 - pod/custom/tenants/clearsessions.sh | 3 - pod/custom/tenants/create_tenant.sh | 4 + pod/custom/tenants/index_videos.sh | 3 - pod/custom/tenants/live_viewcounter.sh | 3 - pod/settings.py | 85 ++++++++++++--------- 6 files changed, 54 insertions(+), 48 deletions(-) delete mode 100644 pod/custom/tenants/check_obsolete_videos.sh delete mode 100644 pod/custom/tenants/clearsessions.sh delete mode 100644 pod/custom/tenants/index_videos.sh delete mode 100644 pod/custom/tenants/live_viewcounter.sh diff --git a/pod/custom/tenants/check_obsolete_videos.sh b/pod/custom/tenants/check_obsolete_videos.sh deleted file mode 100644 index 70cd0d67e7..0000000000 --- a/pod/custom/tenants/check_obsolete_videos.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -# Main -cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py check_obsolete_videos >> /usr/local/django_projects/podv2/pod/log/cron_obsolete.log 2>&1 \ No newline at end of file diff --git a/pod/custom/tenants/clearsessions.sh b/pod/custom/tenants/clearsessions.sh deleted file mode 100644 index 4260a68d73..0000000000 --- a/pod/custom/tenants/clearsessions.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# main -cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py clearsessions &>> /usr/local/django_projects/podv2/pod/log/cron_clearsessions.log 2>&1 \ No newline at end of file diff --git a/pod/custom/tenants/create_tenant.sh b/pod/custom/tenants/create_tenant.sh index ed77b241c9..b1b967710e 100644 --- a/pod/custom/tenants/create_tenant.sh +++ b/pod/custom/tenants/create_tenant.sh @@ -73,18 +73,22 @@ echo "(django_pod) pod@pod:/usr/local/django_projects/podv2$ python manage.py cr echo "--" echo "crontab" echo "clear session" +echo "" >> $BASEDIR/clearsessions.sh echo "# $ID_SITE $NAME " >> $BASEDIR/clearsessions.sh echo "cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py clearsessions --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv2/pod/log/cron_clearsessions_$NAME.log 2>&1" >> $BASEDIR/clearsessions.sh echo "index videos" +echo "" >> $BASEDIR/index_videos.sh echo "# $ID_SITE $NAME " >> $BASEDIR/index_videos.sh echo "cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py index_videos --all --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv2/pod/log/cron_index_$NAME.log 2>&1" >> $BASEDIR/index_videos.sh echo "check_obsolete_videos" +echo "" >> $BASEDIR/check_obsolete_videos.sh echo "# $ID_SITE $NAME " >> $BASEDIR/check_obsolete_videos.sh echo "cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py check_obsolete_videos --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv2/pod/log/cron_obsolete_$NAME.log 2>&1" >> $BASEDIR/check_obsolete_videos.sh echo "live_viewcounter" +echo "" >> $BASEDIR/live_viewcounter.sh echo "# $ID_SITE $NAME " >> $BASEDIR/live_viewcounter.sh echo "cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py live_viewcounter --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv2/pod/log/cron_viewcounter_$NAME.log 2>&1" >> $BASEDIR/live_viewcounter.sh diff --git a/pod/custom/tenants/index_videos.sh b/pod/custom/tenants/index_videos.sh deleted file mode 100644 index 066e10e80a..0000000000 --- a/pod/custom/tenants/index_videos.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# main -cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py index_videos --all &>> /usr/local/django_projects/podv2/pod/log/cron_index.log 2>&1 \ No newline at end of file diff --git a/pod/custom/tenants/live_viewcounter.sh b/pod/custom/tenants/live_viewcounter.sh deleted file mode 100644 index a3c798c22a..0000000000 --- a/pod/custom/tenants/live_viewcounter.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Main -cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py live_viewcounter >> /usr/local/django_projects/podv2/pod/log/cron_viewcounter.log 2>&1 diff --git a/pod/settings.py b/pod/settings.py index 5826b5d6e3..e9c114b029 100644 --- a/pod/settings.py +++ b/pod/settings.py @@ -231,43 +231,58 @@ for variable in dir(_temp.settings_local): if variable == variable.upper(): locals()[variable] = getattr(_temp.settings_local, variable) -## -# AUTH CAS -# -if "USE_CAS" in globals() and eval("USE_CAS") is True: - AUTHENTICATION_BACKENDS = ( - "pod.main.auth_backend.SiteBackend", - "cas.backends.CASBackend", - ) - CAS_RESPONSE_CALLBACKS = ( - "pod.authentication.populatedCASbackend.populateUser", - # function call to add some information to user login by CAS - ) - MIDDLEWARE.append("cas.middleware.CASMiddleware") +# add tenant settings +def update_tenant_settings(tenant): + path = "pod/custom/tenants/%(tenant)s/%(tenant)s_settings.py" %{"tenant": tenant} + application = path.replace(os.path.sep, ".").replace(".py", "") + settings = "%s_settings" % tenant + if os.path.exists(path): + _temp = __import__(application, globals(), locals(), [settings]) + for variable in dir(_temp): + if variable == variable.upper(): + globals()[variable] = getattr(_temp, variable) + update_settings() -if "USE_SHIB" in globals() and eval("USE_SHIB") is True: - AUTHENTICATION_BACKENDS += ("pod.authentication.backends.ShibbBackend",) - MIDDLEWARE.append("pod.authentication.shibmiddleware.ShibbMiddleware") +def update_settings(): + global AUTHENTICATION_BACKENDS, TEMPLATES, CAS_RESPONSE_CALLBACKS, MIDDLEWARE + ## + # AUTH + # + if "USE_CAS" in globals() and eval("USE_CAS") is True: + AUTHENTICATION_BACKENDS += ( + "cas.backends.CASBackend", + ) + CAS_RESPONSE_CALLBACKS = ( + "pod.authentication.populatedCASbackend.populateUser", + # function call to add some information to user login by CAS + ) + MIDDLEWARE.append("cas.middleware.CASMiddleware") -if "USE_OIDC" in globals() and eval("USE_OIDC") is True: - AUTHENTICATION_BACKENDS += ("pod.authentication.backends.OIDCBackend",) - LOGIN_REDIRECT_URL = "/" + if "USE_SHIB" in globals() and eval("USE_SHIB") is True: + AUTHENTICATION_BACKENDS += ("pod.authentication.backends.ShibbBackend",) + MIDDLEWARE.append("pod.authentication.shibmiddleware.ShibbMiddleware") -## -# Authentication backend : add lti backend if use -# -if "LTI_ENABLED" in globals() and eval("LTI_ENABLED") is True: - AUTHENTICATION_BACKENDS = list(AUTHENTICATION_BACKENDS) - AUTHENTICATION_BACKENDS.append("lti_provider.auth.LTIBackend") - AUTHENTICATION_BACKENDS = tuple(AUTHENTICATION_BACKENDS) + if "USE_OIDC" in globals() and eval("USE_OIDC") is True: + AUTHENTICATION_BACKENDS += ("pod.authentication.backends.OIDCBackend",) + LOGIN_REDIRECT_URL = "/" -if "H5P_ENABLED" in globals() and eval("H5P_ENABLED") is True: - sys.path.append(os.path.join(BASE_DIR, "../../H5PP")) - INSTALLED_APPS.append("h5pp") - INSTALLED_APPS.append("pod.interactive") + ## + # Authentication backend : add lti backend if use + # + if "LTI_ENABLED" in globals() and eval("LTI_ENABLED") is True: + AUTHENTICATION_BACKENDS += ("lti_provider.auth.LTIBackend",) -## -# Opencast studio -if "USE_OPENCAST_STUDIO" in globals() and eval("USE_OPENCAST_STUDIO") is True: - # add dir to opencast studio static files i.e : pod/custom/static/opencast/ - TEMPLATES[0]["DIRS"].append(os.path.join(BASE_DIR, "custom", "static", "opencast")) + if "H5P_ENABLED" in globals() and eval("H5P_ENABLED") is True: + sys.path.append(os.path.join(BASE_DIR, "../../H5PP")) + INSTALLED_APPS.append("h5pp") + INSTALLED_APPS.append("pod.interactive") + + ## + # Opencast studio + if "USE_OPENCAST_STUDIO" in globals() and eval("USE_OPENCAST_STUDIO") is True: + # add dir to opencast studio static files i.e : pod/custom/static/opencast/ + TEMPLATES[0]["DIRS"].append(os.path.join(BASE_DIR, "custom", "static", "opencast")) + + AUTHENTICATION_BACKENDS = list(dict.fromkeys(AUTHENTICATION_BACKENDS)) + +update_settings() \ No newline at end of file From 5dd19ac9477286da6dba90d1fa9294a087f172ce Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 4 May 2022 17:22:20 +0200 Subject: [PATCH 08/45] change create tenant to add info inside sh_tenant files --- pod/custom/tenants/create_tenant.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pod/custom/tenants/create_tenant.sh b/pod/custom/tenants/create_tenant.sh index b1b967710e..9da2138d83 100644 --- a/pod/custom/tenants/create_tenant.sh +++ b/pod/custom/tenants/create_tenant.sh @@ -73,23 +73,23 @@ echo "(django_pod) pod@pod:/usr/local/django_projects/podv2$ python manage.py cr echo "--" echo "crontab" echo "clear session" -echo "" >> $BASEDIR/clearsessions.sh -echo "# $ID_SITE $NAME " >> $BASEDIR/clearsessions.sh +echo "" >> $BASEDIR/sh_tenants/clearsessions.sh +echo "# $ID_SITE $NAME " >> $BASEDIR/sh_tenants/clearsessions.sh echo "cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py clearsessions --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv2/pod/log/cron_clearsessions_$NAME.log 2>&1" >> $BASEDIR/clearsessions.sh echo "index videos" -echo "" >> $BASEDIR/index_videos.sh -echo "# $ID_SITE $NAME " >> $BASEDIR/index_videos.sh +echo "" >> $BASEDIR/sh_tenants/index_videos.sh +echo "# $ID_SITE $NAME " >> $BASEDIR/sh_tenants/index_videos.sh echo "cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py index_videos --all --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv2/pod/log/cron_index_$NAME.log 2>&1" >> $BASEDIR/index_videos.sh echo "check_obsolete_videos" -echo "" >> $BASEDIR/check_obsolete_videos.sh -echo "# $ID_SITE $NAME " >> $BASEDIR/check_obsolete_videos.sh +echo "" >> $BASEDIR/sh_tenants/check_obsolete_videos.sh +echo "# $ID_SITE $NAME " >> $BASEDIR/sh_tenants/check_obsolete_videos.sh echo "cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py check_obsolete_videos --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv2/pod/log/cron_obsolete_$NAME.log 2>&1" >> $BASEDIR/check_obsolete_videos.sh echo "live_viewcounter" -echo "" >> $BASEDIR/live_viewcounter.sh -echo "# $ID_SITE $NAME " >> $BASEDIR/live_viewcounter.sh +echo "" >> $BASEDIR/sh_tenants/live_viewcounter.sh +echo "# $ID_SITE $NAME " >> $BASEDIR/sh_tenants/live_viewcounter.sh echo "cd /usr/local/django_projects/podv2 && /home/pod/.virtualenvs/django_pod/bin/python manage.py live_viewcounter --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv2/pod/log/cron_viewcounter_$NAME.log 2>&1" >> $BASEDIR/live_viewcounter.sh echo "FIN" From 5344f6d1ab8eda090d2abbfd9e5baa7170f0ff73 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 4 May 2022 17:27:40 +0200 Subject: [PATCH 09/45] update settings to flake8 compliant --- pod/settings.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pod/settings.py b/pod/settings.py index e9c114b029..29e75ef74c 100644 --- a/pod/settings.py +++ b/pod/settings.py @@ -231,9 +231,11 @@ for variable in dir(_temp.settings_local): if variable == variable.upper(): locals()[variable] = getattr(_temp.settings_local, variable) + + # add tenant settings def update_tenant_settings(tenant): - path = "pod/custom/tenants/%(tenant)s/%(tenant)s_settings.py" %{"tenant": tenant} + path = "pod/custom/tenants/%(tenant)s/%(tenant)s_settings.py" % {"tenant": tenant} application = path.replace(os.path.sep, ".").replace(".py", "") settings = "%s_settings" % tenant if os.path.exists(path): @@ -243,8 +245,9 @@ def update_tenant_settings(tenant): globals()[variable] = getattr(_temp, variable) update_settings() + def update_settings(): - global AUTHENTICATION_BACKENDS, TEMPLATES, CAS_RESPONSE_CALLBACKS, MIDDLEWARE + global AUTHENTICATION_BACKENDS, TEMPLATES, CAS_RESPONSE_CALLBACKS, MIDDLEWARE, LOGIN_REDIRECT_URL ## # AUTH # @@ -282,7 +285,8 @@ def update_settings(): if "USE_OPENCAST_STUDIO" in globals() and eval("USE_OPENCAST_STUDIO") is True: # add dir to opencast studio static files i.e : pod/custom/static/opencast/ TEMPLATES[0]["DIRS"].append(os.path.join(BASE_DIR, "custom", "static", "opencast")) - + AUTHENTICATION_BACKENDS = list(dict.fromkeys(AUTHENTICATION_BACKENDS)) -update_settings() \ No newline at end of file + +update_settings() From 4c12a36083b5a594aa1bc72258828996c64d268e Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 5 May 2022 12:41:59 +0000 Subject: [PATCH 10/45] Fixup: format code with Black --- pod/settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pod/settings.py b/pod/settings.py index 29e75ef74c..4d05bdd598 100644 --- a/pod/settings.py +++ b/pod/settings.py @@ -252,9 +252,7 @@ def update_settings(): # AUTH # if "USE_CAS" in globals() and eval("USE_CAS") is True: - AUTHENTICATION_BACKENDS += ( - "cas.backends.CASBackend", - ) + AUTHENTICATION_BACKENDS += ("cas.backends.CASBackend",) CAS_RESPONSE_CALLBACKS = ( "pod.authentication.populatedCASbackend.populateUser", # function call to add some information to user login by CAS @@ -284,7 +282,9 @@ def update_settings(): # Opencast studio if "USE_OPENCAST_STUDIO" in globals() and eval("USE_OPENCAST_STUDIO") is True: # add dir to opencast studio static files i.e : pod/custom/static/opencast/ - TEMPLATES[0]["DIRS"].append(os.path.join(BASE_DIR, "custom", "static", "opencast")) + TEMPLATES[0]["DIRS"].append( + os.path.join(BASE_DIR, "custom", "static", "opencast") + ) AUTHENTICATION_BACKENDS = list(dict.fromkeys(AUTHENTICATION_BACKENDS)) From a5af5c0d95010707241cdb4de3dd6c3394a73fb4 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 5 May 2022 15:01:40 +0200 Subject: [PATCH 11/45] =?UTF-8?q?add=20calling=20to=20update=20tenant=20se?= =?UTF-8?q?ttings=20=C3=A0=20the=20end=20of=20source=20tenant=20settigns?= =?UTF-8?q?=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pod/custom/tenants/source/tenant_settings.py | 2 ++ pod/custom/tenants/source_enc/tenant_enc_settings.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pod/custom/tenants/source/tenant_settings.py b/pod/custom/tenants/source/tenant_settings.py index 509afced03..f6250d6f5f 100644 --- a/pod/custom/tenants/source/tenant_settings.py +++ b/pod/custom/tenants/source/tenant_settings.py @@ -44,3 +44,5 @@ DEFAULT_DC_RIGHTS = "BY-NC-SA" CELERY_BROKER_URL = CELERY_BROKER_URL + "-__NAME__" # Define a broker + +update_tenant_settings("__NAME__") diff --git a/pod/custom/tenants/source_enc/tenant_enc_settings.py b/pod/custom/tenants/source_enc/tenant_enc_settings.py index bac9727254..d2780209c8 100644 --- a/pod/custom/tenants/source_enc/tenant_enc_settings.py +++ b/pod/custom/tenants/source_enc/tenant_enc_settings.py @@ -21,3 +21,5 @@ CELERY_TO_ENCODE = True # Active encode CELERY_BROKER_URL = CELERY_BROKER_URL + "-__NAME__" # Define a broker + +update_tenant_settings("__NAME__") From 3b711c2251b78f565c07dbfca384290da5eef027 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 5 May 2022 15:27:59 +0200 Subject: [PATCH 12/45] fix video count by site for type and discipline in context --- pod/main/context_processors.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pod/main/context_processors.py b/pod/main/context_processors.py index 8e68312eac..e764299736 100644 --- a/pod/main/context_processors.py +++ b/pod/main/context_processors.py @@ -1,6 +1,6 @@ from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured -from django.db.models import Count, Sum +from django.db.models import Count, Sum, Q from django.db.models import Prefetch from datetime import timedelta @@ -214,13 +214,18 @@ def context_navbar(request): Type.objects.filter( sites=get_current_site(request), video__is_draft=False, + video__sites=get_current_site(request) ) .distinct() .annotate(video_count=Count("video", distinct=True)) ) disciplines = ( - Discipline.objects.filter(video__is_draft=False, sites=get_current_site(request)) + Discipline.objects.filter( + sites=get_current_site(request), + video__is_draft=False, + video__sites=get_current_site(request) + ) .distinct() .annotate(video_count=Count("video", distinct=True)) ) From 04743f4bcde32aab6fbade293738c89dd032b0fc Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 5 May 2022 15:34:10 +0200 Subject: [PATCH 13/45] remove unused import in context proc --- pod/main/context_processors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pod/main/context_processors.py b/pod/main/context_processors.py index e764299736..9a0c5cd850 100644 --- a/pod/main/context_processors.py +++ b/pod/main/context_processors.py @@ -1,6 +1,6 @@ from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured -from django.db.models import Count, Sum, Q +from django.db.models import Count, Sum from django.db.models import Prefetch from datetime import timedelta From c71bfa02498e6af6fa97e1845be1aa54bf3c0845 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 5 May 2022 16:54:23 +0200 Subject: [PATCH 14/45] put is_superuser field in readonly for non superuser --- pod/authentication/admin.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pod/authentication/admin.py b/pod/authentication/admin.py index 10b50c9744..3b3384c932 100644 --- a/pod/authentication/admin.py +++ b/pod/authentication/admin.py @@ -113,6 +113,13 @@ def clickable_email(self, obj): if USE_ESTABLISHMENT_FIELD: list_display = list_display + ("owner_establishment",) + # readonly_fields=('is_superuser',) + def get_readonly_fields(self, request, obj=None): + if request.user.is_superuser: + return [] + self.readonly_fields += ('is_superuser',) + return self.readonly_fields + def owner_hashkey(self, obj): return "%s" % Owner.objects.get(user=obj).hashkey From 62b320df94a124394b2eb5313ef7e7b02617002e Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 9 May 2022 16:16:42 +0200 Subject: [PATCH 15/45] new solution to update settings from tenant --- pod/custom/tenants/source/tenant_settings.py | 6 +- pod/settings.py | 64 +++++++++----------- 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/pod/custom/tenants/source/tenant_settings.py b/pod/custom/tenants/source/tenant_settings.py index f6250d6f5f..479eff22c3 100644 --- a/pod/custom/tenants/source/tenant_settings.py +++ b/pod/custom/tenants/source/tenant_settings.py @@ -45,4 +45,8 @@ CELERY_BROKER_URL = CELERY_BROKER_URL + "-__NAME__" # Define a broker -update_tenant_settings("__NAME__") + +########################################################## A LAISSER EN DERNIER !!!!!!!!!!!!! +the_update_settings = update_settings(locals()) +for variable in the_update_settings: + locals()[variable] = the_update_settings[variable] diff --git a/pod/settings.py b/pod/settings.py index 29e75ef74c..5e5b0fdaad 100644 --- a/pod/settings.py +++ b/pod/settings.py @@ -233,60 +233,50 @@ locals()[variable] = getattr(_temp.settings_local, variable) -# add tenant settings -def update_tenant_settings(tenant): - path = "pod/custom/tenants/%(tenant)s/%(tenant)s_settings.py" % {"tenant": tenant} - application = path.replace(os.path.sep, ".").replace(".py", "") - settings = "%s_settings" % tenant - if os.path.exists(path): - _temp = __import__(application, globals(), locals(), [settings]) - for variable in dir(_temp): - if variable == variable.upper(): - globals()[variable] = getattr(_temp, variable) - update_settings() - - -def update_settings(): - global AUTHENTICATION_BACKENDS, TEMPLATES, CAS_RESPONSE_CALLBACKS, MIDDLEWARE, LOGIN_REDIRECT_URL +def update_settings(local_settings): ## # AUTH # - if "USE_CAS" in globals() and eval("USE_CAS") is True: - AUTHENTICATION_BACKENDS += ( + if local_settings.get("USE_CAS", False): + local_settings["AUTHENTICATION_BACKENDS"] += ( "cas.backends.CASBackend", ) - CAS_RESPONSE_CALLBACKS = ( + local_settings["CAS_RESPONSE_CALLBACKS"] = ( "pod.authentication.populatedCASbackend.populateUser", # function call to add some information to user login by CAS ) - MIDDLEWARE.append("cas.middleware.CASMiddleware") - - if "USE_SHIB" in globals() and eval("USE_SHIB") is True: - AUTHENTICATION_BACKENDS += ("pod.authentication.backends.ShibbBackend",) - MIDDLEWARE.append("pod.authentication.shibmiddleware.ShibbMiddleware") - - if "USE_OIDC" in globals() and eval("USE_OIDC") is True: - AUTHENTICATION_BACKENDS += ("pod.authentication.backends.OIDCBackend",) - LOGIN_REDIRECT_URL = "/" - + local_settings["MIDDLEWARE"].append("cas.middleware.CASMiddleware") + + if local_settings.get("USE_SHIB", False): + local_settings["AUTHENTICATION_BACKENDS"] += ("pod.authentication.backends.ShibbBackend",) + local_settings["MIDDLEWARE"].append("pod.authentication.shibmiddleware.ShibbMiddleware") + if local_settings.get("USE_OIDC", False): + local_settings["AUTHENTICATION_BACKENDS"] += ("pod.authentication.backends.OIDCBackend",) + local_settings['LOGIN_REDIRECT_URL'] = "/" ## # Authentication backend : add lti backend if use # - if "LTI_ENABLED" in globals() and eval("LTI_ENABLED") is True: - AUTHENTICATION_BACKENDS += ("lti_provider.auth.LTIBackend",) + if local_settings.get("LTI_ENABLED", False): + local_settings["AUTHENTICATION_BACKENDS"] += ("lti_provider.auth.LTIBackend",) - if "H5P_ENABLED" in globals() and eval("H5P_ENABLED") is True: + if local_settings.get("H5P_ENABLED", False): sys.path.append(os.path.join(BASE_DIR, "../../H5PP")) - INSTALLED_APPS.append("h5pp") - INSTALLED_APPS.append("pod.interactive") + local_settings["INSTALLED_APPS"].append("h5pp") + local_settings["INSTALLED_APPS"].append("pod.interactive") ## # Opencast studio - if "USE_OPENCAST_STUDIO" in globals() and eval("USE_OPENCAST_STUDIO") is True: + if local_settings.get("USE_OPENCAST_STUDIO", False): # add dir to opencast studio static files i.e : pod/custom/static/opencast/ - TEMPLATES[0]["DIRS"].append(os.path.join(BASE_DIR, "custom", "static", "opencast")) + local_settings["TEMPLATES"][0]["DIRS"].append(os.path.join(BASE_DIR, "custom", "static", "opencast")) + + local_settings["AUTHENTICATION_BACKENDS"] = list(dict.fromkeys(local_settings["AUTHENTICATION_BACKENDS"])) - AUTHENTICATION_BACKENDS = list(dict.fromkeys(AUTHENTICATION_BACKENDS)) + return local_settings -update_settings() +the_update_settings = update_settings( + locals() +) +for variable in the_update_settings: + locals()[variable] = the_update_settings[variable] From 082bbcf128e9b2daa5678ffa8e5fdca0cb1195cc Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 9 May 2022 16:23:34 +0200 Subject: [PATCH 16/45] add update settings for enodage settings --- pod/custom/tenants/source_enc/tenant_enc_settings.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pod/custom/tenants/source_enc/tenant_enc_settings.py b/pod/custom/tenants/source_enc/tenant_enc_settings.py index d2780209c8..1660130d4b 100644 --- a/pod/custom/tenants/source_enc/tenant_enc_settings.py +++ b/pod/custom/tenants/source_enc/tenant_enc_settings.py @@ -22,4 +22,7 @@ CELERY_TO_ENCODE = True # Active encode CELERY_BROKER_URL = CELERY_BROKER_URL + "-__NAME__" # Define a broker -update_tenant_settings("__NAME__") +########################################################## A LAISSER EN DERNIER !!!!!!!!!!!!! +the_update_settings = update_settings(locals()) +for variable in the_update_settings: + locals()[variable] = the_update_settings[variable] From 80d16c1a254a9c10419cc64024f8ec25d2fd5051 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 12 May 2022 15:31:23 +0200 Subject: [PATCH 17/45] filter tags by site for template --- pod/main/templates/aside.html | 2 +- pod/video/templates/videos/filter_aside.html | 2 +- pod/video/templatetags/video_tags.py | 186 +++++++++++++++++++ 3 files changed, 188 insertions(+), 2 deletions(-) diff --git a/pod/main/templates/aside.html b/pod/main/templates/aside.html index 8f29ebe10e..a941ece2ba 100644 --- a/pod/main/templates/aside.html +++ b/pod/main/templates/aside.html @@ -1,5 +1,5 @@ {% load i18n %} -{% load tagging_tags %} +{% load video_tags %} {% spaceless %} {% if HIDE_SHARE == False %} diff --git a/pod/video/templates/videos/filter_aside.html b/pod/video/templates/videos/filter_aside.html index 15b13fdf62..7c2b6fffb3 100644 --- a/pod/video/templates/videos/filter_aside.html +++ b/pod/video/templates/videos/filter_aside.html @@ -1,5 +1,5 @@ {% load i18n %} -{% load tagging_tags %} +{% load video_tags %} {% load thumbnail %} {% spaceless %} diff --git a/pod/video/templatetags/video_tags.py b/pod/video/templatetags/video_tags.py index ab76172a07..2a65f4df9d 100644 --- a/pod/video/templatetags/video_tags.py +++ b/pod/video/templatetags/video_tags.py @@ -4,6 +4,14 @@ from django.urls import reverse from django.db.models import Q from django.contrib.sites.shortcuts import get_current_site +from django.template import TemplateSyntaxError +from django.apps.registry import apps +from django.utils.translation import ugettext_lazy as _ + +from tagging.templatetags.tagging_tags import TagsForModelNode, TagCloudForModelNode +from tagging.models import Tag +from tagging.utils import LINEAR +from tagging.utils import LOGARITHMIC from ..forms import VideoVersionForm from ..models import Video @@ -72,3 +80,181 @@ def get_last_videos(context): break return recent_vids + + +class getTagsForModelNode(TagsForModelNode): + def __init__(self, model, context_var, counts): + super(getTagsForModelNode, self).__init__(model, context_var, counts) + + def render(self, context): + model = apps.get_model(*self.model.split('.')) + if model is None: + raise TemplateSyntaxError( + _('tags_for_model tag was given an invalid model: %s') % + self.model) + request = context["request"] + context[self.context_var] = Tag.objects.usage_for_model( + model, counts=self.counts, filters=dict(is_draft=False, sites=get_current_site(request))) + return '' + + +def do_tags_for_model(parser, token): + """ + Retrieves a list of ``Tag`` objects associated with a given model + and stores them in a context variable. + + Usage:: + + {% tags_for_model [model] as [varname] %} + + The model is specified in ``[appname].[modelname]`` format. + + Extended usage:: + + {% tags_for_model [model] as [varname] with counts %} + + If specified - by providing extra ``with counts`` arguments - adds + a ``count`` attribute to each tag containing the number of + instances of the given model which have been tagged with it. + + Examples:: + + {% tags_for_model products.Widget as widget_tags %} + {% tags_for_model products.Widget as widget_tags with counts %} + + """ + bits = token.contents.split() + len_bits = len(bits) + if len_bits not in (4, 6): + raise TemplateSyntaxError( + _('%s tag requires either three or five arguments') % bits[0]) + if bits[2] != 'as': + raise TemplateSyntaxError( + _("second argument to %s tag must be 'as'") % bits[0]) + if len_bits == 6: + if bits[4] != 'with': + raise TemplateSyntaxError( + _("if given, fourth argument to %s tag must be 'with'") % + bits[0]) + if bits[5] != 'counts': + raise TemplateSyntaxError( + _("if given, fifth argument to %s tag must be 'counts'") % + bits[0]) + if len_bits == 4: + return getTagsForModelNode(bits[1], bits[3], counts=False) + else: + return getTagsForModelNode(bits[1], bits[3], counts=True) + + +def do_tag_cloud_for_model(parser, token): + """ + Retrieves a list of ``Tag`` objects for a given model, with tag + cloud attributes set, and stores them in a context variable. + + Usage:: + + {% tag_cloud_for_model [model] as [varname] %} + + The model is specified in ``[appname].[modelname]`` format. + + Extended usage:: + + {% tag_cloud_for_model [model] as [varname] with [options] %} + + Extra options can be provided after an optional ``with`` argument, + with each option being specified in ``[name]=[value]`` format. Valid + extra options are: + + ``steps`` + Integer. Defines the range of font sizes. + + ``min_count`` + Integer. Defines the minimum number of times a tag must have + been used to appear in the cloud. + + ``distribution`` + One of ``linear`` or ``log``. Defines the font-size + distribution algorithm to use when generating the tag cloud. + + Examples:: + + {% tag_cloud_for_model products.Widget as widget_tags %} + {% tag_cloud_for_model products.Widget as widget_tags + with steps=9 min_count=3 distribution=log %} + + """ + bits = token.contents.split() + len_bits = len(bits) + if len_bits != 4 and len_bits not in range(6, 9): + raise TemplateSyntaxError( + _('%s tag requires either three or between five ' + 'and seven arguments') % bits[0]) + if bits[2] != 'as': + raise TemplateSyntaxError( + _("second argument to %s tag must be 'as'") % bits[0]) + kwargs = {} + if len_bits > 5: + if bits[4] != 'with': + raise TemplateSyntaxError( + _("if given, fourth argument to %s tag must be 'with'") % + bits[0]) + kwargs = get_kwargs_for_cloud(len_bits, bits) + return TagCloudForModelNode(bits[1], bits[3], **kwargs) + + +def get_kwargs_for_cloud(len_bits, bits): + kwargs = {} + for i in range(5, len_bits): + try: + name, value = bits[i].split('=') + if name in ['steps', 'min_count', 'distribution']: + kwargs = update_kwargs_from_bits(kwargs, name, value, bits) + else: + raise TemplateSyntaxError( + _("%(tag)s tag was given an " + "invalid option: '%(option)s'") % { + 'tag': bits[0], + 'option': name, + }) + except ValueError: + raise TemplateSyntaxError( + _("%(tag)s tag was given a badly " + "formatted option: '%(option)s'") % { + 'tag': bits[0], + 'option': bits[i], + }) + filters = dict(is_draft=False, sites=get_current_site(None)) + kwargs["filters"] = filters + return kwargs + + +def update_kwargs_from_bits(kwargs, name, value, bits): + if name == 'steps' or name == 'min_count': + try: + kwargs[str(name)] = int(value) + except ValueError: + raise TemplateSyntaxError( + _("%(tag)s tag's '%(option)s' option was not " + "a valid integer: '%(value)s'") % { + 'tag': bits[0], + 'option': name, + 'value': value, + }) + if name == 'distribution': + if value in ['linear', 'log']: + kwargs[str(name)] = { + 'linear': LINEAR, + 'log': LOGARITHMIC}[value] + else: + raise TemplateSyntaxError( + _("%(tag)s tag's '%(option)s' option was not " + "a valid choice: '%(value)s'") % { + 'tag': bits[0], + 'option': name, + 'value': value, + }) + return kwargs + + +register.tag('tags_for_model', do_tags_for_model) +register.tag('tag_cloud_for_model', do_tag_cloud_for_model) From 84fd2acf0ec19e0d243bfbebe7aa6a614ab4b285 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 12 May 2022 13:42:28 +0000 Subject: [PATCH 18/45] Fixup: format code with Black --- pod/authentication/admin.py | 2 +- pod/main/context_processors.py | 4 +- pod/settings.py | 30 ++++--- pod/video/templatetags/video_tags.py | 121 +++++++++++++++------------ 4 files changed, 87 insertions(+), 70 deletions(-) diff --git a/pod/authentication/admin.py b/pod/authentication/admin.py index 3b3384c932..8a3ffa6388 100644 --- a/pod/authentication/admin.py +++ b/pod/authentication/admin.py @@ -117,7 +117,7 @@ def clickable_email(self, obj): def get_readonly_fields(self, request, obj=None): if request.user.is_superuser: return [] - self.readonly_fields += ('is_superuser',) + self.readonly_fields += ("is_superuser",) return self.readonly_fields def owner_hashkey(self, obj): diff --git a/pod/main/context_processors.py b/pod/main/context_processors.py index 9a0c5cd850..f1b5113e54 100644 --- a/pod/main/context_processors.py +++ b/pod/main/context_processors.py @@ -214,7 +214,7 @@ def context_navbar(request): Type.objects.filter( sites=get_current_site(request), video__is_draft=False, - video__sites=get_current_site(request) + video__sites=get_current_site(request), ) .distinct() .annotate(video_count=Count("video", distinct=True)) @@ -224,7 +224,7 @@ def context_navbar(request): Discipline.objects.filter( sites=get_current_site(request), video__is_draft=False, - video__sites=get_current_site(request) + video__sites=get_current_site(request), ) .distinct() .annotate(video_count=Count("video", distinct=True)) diff --git a/pod/settings.py b/pod/settings.py index 5e5b0fdaad..f46c43b9b8 100644 --- a/pod/settings.py +++ b/pod/settings.py @@ -238,9 +238,7 @@ def update_settings(local_settings): # AUTH # if local_settings.get("USE_CAS", False): - local_settings["AUTHENTICATION_BACKENDS"] += ( - "cas.backends.CASBackend", - ) + local_settings["AUTHENTICATION_BACKENDS"] += ("cas.backends.CASBackend",) local_settings["CAS_RESPONSE_CALLBACKS"] = ( "pod.authentication.populatedCASbackend.populateUser", # function call to add some information to user login by CAS @@ -248,11 +246,17 @@ def update_settings(local_settings): local_settings["MIDDLEWARE"].append("cas.middleware.CASMiddleware") if local_settings.get("USE_SHIB", False): - local_settings["AUTHENTICATION_BACKENDS"] += ("pod.authentication.backends.ShibbBackend",) - local_settings["MIDDLEWARE"].append("pod.authentication.shibmiddleware.ShibbMiddleware") + local_settings["AUTHENTICATION_BACKENDS"] += ( + "pod.authentication.backends.ShibbBackend", + ) + local_settings["MIDDLEWARE"].append( + "pod.authentication.shibmiddleware.ShibbMiddleware" + ) if local_settings.get("USE_OIDC", False): - local_settings["AUTHENTICATION_BACKENDS"] += ("pod.authentication.backends.OIDCBackend",) - local_settings['LOGIN_REDIRECT_URL'] = "/" + local_settings["AUTHENTICATION_BACKENDS"] += ( + "pod.authentication.backends.OIDCBackend", + ) + local_settings["LOGIN_REDIRECT_URL"] = "/" ## # Authentication backend : add lti backend if use # @@ -268,15 +272,17 @@ def update_settings(local_settings): # Opencast studio if local_settings.get("USE_OPENCAST_STUDIO", False): # add dir to opencast studio static files i.e : pod/custom/static/opencast/ - local_settings["TEMPLATES"][0]["DIRS"].append(os.path.join(BASE_DIR, "custom", "static", "opencast")) + local_settings["TEMPLATES"][0]["DIRS"].append( + os.path.join(BASE_DIR, "custom", "static", "opencast") + ) - local_settings["AUTHENTICATION_BACKENDS"] = list(dict.fromkeys(local_settings["AUTHENTICATION_BACKENDS"])) + local_settings["AUTHENTICATION_BACKENDS"] = list( + dict.fromkeys(local_settings["AUTHENTICATION_BACKENDS"]) + ) return local_settings -the_update_settings = update_settings( - locals() -) +the_update_settings = update_settings(locals()) for variable in the_update_settings: locals()[variable] = the_update_settings[variable] diff --git a/pod/video/templatetags/video_tags.py b/pod/video/templatetags/video_tags.py index 2a65f4df9d..d5f067c5be 100644 --- a/pod/video/templatetags/video_tags.py +++ b/pod/video/templatetags/video_tags.py @@ -87,15 +87,18 @@ def __init__(self, model, context_var, counts): super(getTagsForModelNode, self).__init__(model, context_var, counts) def render(self, context): - model = apps.get_model(*self.model.split('.')) + model = apps.get_model(*self.model.split(".")) if model is None: raise TemplateSyntaxError( - _('tags_for_model tag was given an invalid model: %s') % - self.model) + _("tags_for_model tag was given an invalid model: %s") % self.model + ) request = context["request"] context[self.context_var] = Tag.objects.usage_for_model( - model, counts=self.counts, filters=dict(is_draft=False, sites=get_current_site(request))) - return '' + model, + counts=self.counts, + filters=dict(is_draft=False, sites=get_current_site(request)), + ) + return "" def do_tags_for_model(parser, token): @@ -127,19 +130,19 @@ def do_tags_for_model(parser, token): len_bits = len(bits) if len_bits not in (4, 6): raise TemplateSyntaxError( - _('%s tag requires either three or five arguments') % bits[0]) - if bits[2] != 'as': - raise TemplateSyntaxError( - _("second argument to %s tag must be 'as'") % bits[0]) + _("%s tag requires either three or five arguments") % bits[0] + ) + if bits[2] != "as": + raise TemplateSyntaxError(_("second argument to %s tag must be 'as'") % bits[0]) if len_bits == 6: - if bits[4] != 'with': + if bits[4] != "with": raise TemplateSyntaxError( - _("if given, fourth argument to %s tag must be 'with'") % - bits[0]) - if bits[5] != 'counts': + _("if given, fourth argument to %s tag must be 'with'") % bits[0] + ) + if bits[5] != "counts": raise TemplateSyntaxError( - _("if given, fifth argument to %s tag must be 'counts'") % - bits[0]) + _("if given, fifth argument to %s tag must be 'counts'") % bits[0] + ) if len_bits == 4: return getTagsForModelNode(bits[1], bits[3], counts=False) else: @@ -187,17 +190,17 @@ def do_tag_cloud_for_model(parser, token): len_bits = len(bits) if len_bits != 4 and len_bits not in range(6, 9): raise TemplateSyntaxError( - _('%s tag requires either three or between five ' - 'and seven arguments') % bits[0]) - if bits[2] != 'as': - raise TemplateSyntaxError( - _("second argument to %s tag must be 'as'") % bits[0]) + _("%s tag requires either three or between five " "and seven arguments") + % bits[0] + ) + if bits[2] != "as": + raise TemplateSyntaxError(_("second argument to %s tag must be 'as'") % bits[0]) kwargs = {} if len_bits > 5: - if bits[4] != 'with': + if bits[4] != "with": raise TemplateSyntaxError( - _("if given, fourth argument to %s tag must be 'with'") % - bits[0]) + _("if given, fourth argument to %s tag must be 'with'") % bits[0] + ) kwargs = get_kwargs_for_cloud(len_bits, bits) return TagCloudForModelNode(bits[1], bits[3], **kwargs) @@ -206,55 +209,63 @@ def get_kwargs_for_cloud(len_bits, bits): kwargs = {} for i in range(5, len_bits): try: - name, value = bits[i].split('=') - if name in ['steps', 'min_count', 'distribution']: + name, value = bits[i].split("=") + if name in ["steps", "min_count", "distribution"]: kwargs = update_kwargs_from_bits(kwargs, name, value, bits) else: raise TemplateSyntaxError( - _("%(tag)s tag was given an " - "invalid option: '%(option)s'") % { - 'tag': bits[0], - 'option': name, - }) + _("%(tag)s tag was given an " "invalid option: '%(option)s'") + % { + "tag": bits[0], + "option": name, + } + ) except ValueError: raise TemplateSyntaxError( - _("%(tag)s tag was given a badly " - "formatted option: '%(option)s'") % { - 'tag': bits[0], - 'option': bits[i], - }) + _("%(tag)s tag was given a badly " "formatted option: '%(option)s'") + % { + "tag": bits[0], + "option": bits[i], + } + ) filters = dict(is_draft=False, sites=get_current_site(None)) kwargs["filters"] = filters return kwargs def update_kwargs_from_bits(kwargs, name, value, bits): - if name == 'steps' or name == 'min_count': + if name == "steps" or name == "min_count": try: kwargs[str(name)] = int(value) except ValueError: raise TemplateSyntaxError( - _("%(tag)s tag's '%(option)s' option was not " - "a valid integer: '%(value)s'") % { - 'tag': bits[0], - 'option': name, - 'value': value, - }) - if name == 'distribution': - if value in ['linear', 'log']: - kwargs[str(name)] = { - 'linear': LINEAR, - 'log': LOGARITHMIC}[value] + _( + "%(tag)s tag's '%(option)s' option was not " + "a valid integer: '%(value)s'" + ) + % { + "tag": bits[0], + "option": name, + "value": value, + } + ) + if name == "distribution": + if value in ["linear", "log"]: + kwargs[str(name)] = {"linear": LINEAR, "log": LOGARITHMIC}[value] else: raise TemplateSyntaxError( - _("%(tag)s tag's '%(option)s' option was not " - "a valid choice: '%(value)s'") % { - 'tag': bits[0], - 'option': name, - 'value': value, - }) + _( + "%(tag)s tag's '%(option)s' option was not " + "a valid choice: '%(value)s'" + ) + % { + "tag": bits[0], + "option": name, + "value": value, + } + ) return kwargs -register.tag('tags_for_model', do_tags_for_model) -register.tag('tag_cloud_for_model', do_tag_cloud_for_model) +register.tag("tags_for_model", do_tags_for_model) +register.tag("tag_cloud_for_model", do_tag_cloud_for_model) From 39e636bf6d4ac7c8eae3081740527703bdd461fd Mon Sep 17 00:00:00 2001 From: Quentin Date: Tue, 31 May 2022 12:20:23 +0200 Subject: [PATCH 19/45] Correction lastname --- pod/authentication/populatedCASbackend.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pod/authentication/populatedCASbackend.py b/pod/authentication/populatedCASbackend.py index 9a92aa99fb..36ef2e55ef 100644 --- a/pod/authentication/populatedCASbackend.py +++ b/pod/authentication/populatedCASbackend.py @@ -220,12 +220,19 @@ def populate_user_from_entry(user, owner, entry): else "" ) user.last_name = ( - entry[USER_LDAP_MAPPING_ATTRIBUTES["last_name"]].value - if ( - USER_LDAP_MAPPING_ATTRIBUTES.get("last_name") - and entry[USER_LDAP_MAPPING_ATTRIBUTES["last_name"]] - ) - else "" + entry[USER_LDAP_MAPPING_ATTRIBUTES['last_name']].value + if ( + USER_LDAP_MAPPING_ATTRIBUTES.get('last_name') + and entry[USER_LDAP_MAPPING_ATTRIBUTES['last_name']] + and isinstance(entry[USER_LDAP_MAPPING_ATTRIBUTES['last_name']].value, str) + ) + else entry[USER_LDAP_MAPPING_ATTRIBUTES['last_name']].value[0] + if ( + USER_LDAP_MAPPING_ATTRIBUTES.get('last_name') + and entry[USER_LDAP_MAPPING_ATTRIBUTES['last_name']] + and isinstance(entry[USER_LDAP_MAPPING_ATTRIBUTES['last_name']].value, list) + ) + else "" ) user.save() owner.affiliation = ( From 14b39df573ef18d9745abb2b72c7acbddc6ac0c2 Mon Sep 17 00:00:00 2001 From: MichelAimar <106737905+MichelAimar@users.noreply.github.com> Date: Fri, 3 Jun 2022 16:09:47 +0200 Subject: [PATCH 20/45] add RecordingFileTreatment to rest api add RecordingFileTreatment to rest api --- pod/main/rest_router.py | 1 + pod/recorder/rest_views.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pod/main/rest_router.py b/pod/main/rest_router.py index 1a17e2ccca..3b5247856d 100644 --- a/pod/main/rest_router.py +++ b/pod/main/rest_router.py @@ -56,6 +56,7 @@ router.register(r"recording", recorder_views.RecordingModelViewSet) router.register(r"recordingfile", recorder_views.RecordingFileModelViewSet) +router.register(r"recordingfiletreatment", recorder_views.RecordingFileTreatmentModelViewSet) router.register(r"recorder", recorder_views.RecorderModelViewSet) if getattr(settings, "USE_PODFILE", False): diff --git a/pod/recorder/rest_views.py b/pod/recorder/rest_views.py index dcbdf51d6e..6f43eb5a41 100644 --- a/pod/recorder/rest_views.py +++ b/pod/recorder/rest_views.py @@ -1,5 +1,11 @@ from rest_framework import serializers, viewsets -from .models import RecordingFile, Recording, Recorder +from .models import RecordingFile, Recording, Recorder, RecordingFileTreatment + +class RecordingFileTreatmentModelSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = RecordingFileTreatment + fields = ("id", "file", "file_size", "recorder", "type", "date_added") + filter_fields = ("date_added", "recorder", "file", "id") class RecordingModelSerializer(serializers.HyperlinkedModelSerializer): @@ -20,6 +26,12 @@ class Meta: fields = ("id", "url", "file", "recorder") +class RecordingFileTreatmentModelViewSet(viewsets.ModelViewSet): + queryset = RecordingFileTreatment.objects.all() + serializer_class = RecordingFileTreatmentModelSerializer + filter_fields = ("date_added", "recorder", "file", "id") + + class RecordingModelViewSet(viewsets.ModelViewSet): queryset = Recording.objects.all() serializer_class = RecordingModelSerializer From f66e5f9404b7e2ecf660250dcbbb38063495a8ee Mon Sep 17 00:00:00 2001 From: MichelAimar <106737905+MichelAimar@users.noreply.github.com> Date: Fri, 3 Jun 2022 16:46:23 +0200 Subject: [PATCH 21/45] add filters on user for rest api add filters on user for rest api - needed to import zoom video to pod. --- pod/authentication/rest_views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pod/authentication/rest_views.py b/pod/authentication/rest_views.py index c117d2cb3b..d71fef5e92 100644 --- a/pod/authentication/rest_views.py +++ b/pod/authentication/rest_views.py @@ -56,7 +56,7 @@ class Meta: "email", "groups", ) - + filter_fields = ('id', 'username', 'email') class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: @@ -87,7 +87,7 @@ class OwnerViewSet(viewsets.ModelViewSet): class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all().order_by("-date_joined") serializer_class = UserSerializer - + filter_fields = ('id', 'username', 'email') class GroupViewSet(viewsets.ModelViewSet): queryset = Group.objects.all() From e1555e71581756bb482b509752714bdadb4ccb76 Mon Sep 17 00:00:00 2001 From: MichelAimar <106737905+MichelAimar@users.noreply.github.com> Date: Fri, 3 Jun 2022 16:51:00 +0200 Subject: [PATCH 22/45] add 2 blank lines --- pod/authentication/rest_views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pod/authentication/rest_views.py b/pod/authentication/rest_views.py index d71fef5e92..7480d01ab8 100644 --- a/pod/authentication/rest_views.py +++ b/pod/authentication/rest_views.py @@ -58,6 +58,7 @@ class Meta: ) filter_fields = ('id', 'username', 'email') + class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Group @@ -89,6 +90,7 @@ class UserViewSet(viewsets.ModelViewSet): serializer_class = UserSerializer filter_fields = ('id', 'username', 'email') + class GroupViewSet(viewsets.ModelViewSet): queryset = Group.objects.all() serializer_class = GroupSerializer From f8ee8dd3f136fcae3afc3e52e2817aea30d48c4b Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 9 Jun 2022 08:01:44 +0000 Subject: [PATCH 23/45] Fixup: format code with Black --- pod/authentication/rest_views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pod/authentication/rest_views.py b/pod/authentication/rest_views.py index 7480d01ab8..356d7523a9 100644 --- a/pod/authentication/rest_views.py +++ b/pod/authentication/rest_views.py @@ -56,7 +56,7 @@ class Meta: "email", "groups", ) - filter_fields = ('id', 'username', 'email') + filter_fields = ("id", "username", "email") class GroupSerializer(serializers.HyperlinkedModelSerializer): @@ -88,7 +88,7 @@ class OwnerViewSet(viewsets.ModelViewSet): class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all().order_by("-date_joined") serializer_class = UserSerializer - filter_fields = ('id', 'username', 'email') + filter_fields = ("id", "username", "email") class GroupViewSet(viewsets.ModelViewSet): From f255d14743c7df353049dfa070e9a608b745d81c Mon Sep 17 00:00:00 2001 From: Ptitloup Date: Thu, 9 Jun 2022 10:02:29 +0200 Subject: [PATCH 24/45] Update rest_views.py add to blank line to pep8 compliant --- pod/recorder/rest_views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pod/recorder/rest_views.py b/pod/recorder/rest_views.py index 6f43eb5a41..fdfe17d5bb 100644 --- a/pod/recorder/rest_views.py +++ b/pod/recorder/rest_views.py @@ -1,6 +1,7 @@ from rest_framework import serializers, viewsets from .models import RecordingFile, Recording, Recorder, RecordingFileTreatment + class RecordingFileTreatmentModelSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = RecordingFileTreatment From 9918d5a7e5a681112786bd601d67d6aa6f4dd14c Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 9 Jun 2022 08:07:22 +0000 Subject: [PATCH 25/45] Fixup: format code with Black --- pod/main/rest_router.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pod/main/rest_router.py b/pod/main/rest_router.py index 3b5247856d..63233dac32 100644 --- a/pod/main/rest_router.py +++ b/pod/main/rest_router.py @@ -56,7 +56,9 @@ router.register(r"recording", recorder_views.RecordingModelViewSet) router.register(r"recordingfile", recorder_views.RecordingFileModelViewSet) -router.register(r"recordingfiletreatment", recorder_views.RecordingFileTreatmentModelViewSet) +router.register( + r"recordingfiletreatment", recorder_views.RecordingFileTreatmentModelViewSet +) router.register(r"recorder", recorder_views.RecorderModelViewSet) if getattr(settings, "USE_PODFILE", False): From a4ed0948ba78dbc0bcd074dbdb9e8812a4d140fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9line=20Didier?= Date: Thu, 9 Jun 2022 18:41:25 +0200 Subject: [PATCH 26/45] Add recent view count property to video model fixed to 180 days --- pod/video/models.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pod/video/models.py b/pod/video/models.py index e9e1ce0b54..86224c9512 100755 --- a/pod/video/models.py +++ b/pod/video/models.py @@ -7,6 +7,9 @@ import json import logging import hashlib +import datetime + +from django.utils import timezone from django.db import models from django.conf import settings @@ -859,6 +862,13 @@ def viewcount(self): viewcount.fget.short_description = _("Sum of view") + @property + def recentViewcount(self): + """Get the view counter of a video.""" + return self.get_viewcount(180) + + recentViewcount.fget.short_description = _("Sum of view of last 6 months (180 days)") + @property def get_encoding_step(self): """Get the current encoding step of a video.""" @@ -986,9 +996,16 @@ def get_default_version_link(self, slug_private): else: return version["url"] - def get_viewcount(self): + def get_viewcount(self, from_nb_day=0): """Get the view counter of a video.""" - count_sum = self.viewcount_set.all().aggregate(Sum("count")) + """https://stackoverflow.com/questions/42080864/set-in-django-for-a-queryset""" + set = self.viewcount_set.all(); + if from_nb_day != 0 : + d = datetime.date.today() - timezone.timedelta(days=from_nb_day); + set = self.viewcount_set.filter(date__gte=d); + + count_sum = set.aggregate(Sum("count")) + if count_sum["count__sum"] is None: return 0 return count_sum["count__sum"] From 5dd858f7586bf81a2c7e9b7add603c92802f94b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9line=20Didier?= Date: Thu, 9 Jun 2022 18:57:16 +0200 Subject: [PATCH 27/45] Clean --- pod/video/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pod/video/models.py b/pod/video/models.py index 86224c9512..92bc485dd9 100755 --- a/pod/video/models.py +++ b/pod/video/models.py @@ -998,7 +998,6 @@ def get_default_version_link(self, slug_private): def get_viewcount(self, from_nb_day=0): """Get the view counter of a video.""" - """https://stackoverflow.com/questions/42080864/set-in-django-for-a-queryset""" set = self.viewcount_set.all(); if from_nb_day != 0 : d = datetime.date.today() - timezone.timedelta(days=from_nb_day); From b45abb00c21571e0c23f1e39a787fd1bd50efcc9 Mon Sep 17 00:00:00 2001 From: Julien Gribonvald Date: Fri, 10 Jun 2022 12:11:08 +0200 Subject: [PATCH 28/45] Provide an English description --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ff2843ffc..a43888c5c1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Licence LGPL 3.0](https://img.shields.io/github/license/EsupPortail/Esup-Pod)](https://github.com/EsupPortail/Esup-Pod/blob/master/LICENSE) [![Testing Status](https://github.com/EsupPortail/Esup-Pod/actions/workflows/pod.yml/badge.svg)](https://github.com/EsupPortail/Esup-Pod/actions) [![Coverage Status](https://coveralls.io/repos/github/EsupPortail/Esup-Pod/badge.svg?branch=master)](https://coveralls.io/github/EsupPortail/Esup-Pod?branch=master) - +[FR] ## Plateforme de gestion de fichier vidéo Créé en 2014, le projet Pod a connu de nombreux changement ces dernières années. Initié à l’[Université de Lille](https://www.univ-lille.fr/), il est depuis septembre 2015 piloté par le consortium [Esup Portail](https://www.esup-portail.org/) et soutenu également par le [Ministère de l’Enseignement supérieur, de la Recherche et de l’Innovation](http://www.enseignementsup-recherche.gouv.fr/). @@ -12,6 +12,14 @@ Le projet et la plateforme qui porte le même nom ont pour but de faciliter la m ### Documentation technique * Accédez à toute la documentation (installation, paramétrage etc.) [sur notre wiki](https://www.esup-portail.org/wiki/display/ES/esup-pod "Documentation") +[EN] +## Platform management of video files +Created in 2014 at the university of [Lille](https://www.univ-lille.fr/), the POD project has been managed by the [Esup Portail consortium](https://www.esup-portail.org/) and supported by the [Ministry of Higher Education, Research and Innovation](http://www.enseignementsup-recherche.gouv.fr/) since September 2015 . + +The project and the platform of the same name are aimed at users of our institutions, by allowing the publication of videos in the fields of research (promotion of platforms, etc.), training (tutorials, distance training, student reports, etc.), institutional life (video of events), offering several days of content. + +### Technical documentation +* The documentation (to install, customize, etc...) is on the [ESUP Wiki](https://www.esup-portail.org/wiki/display/ES/esup-pod "Documentation") | | Ministère de lʼEnseignement supérieur, de la Recherche et de lʼInnovation :-----:|:-----:|:----: From 93ebeb0343a2921764885eff9d888d5358804d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9line=20Didier?= Date: Fri, 10 Jun 2022 17:26:23 +0200 Subject: [PATCH 29/45] Correction PR --- pod/custom/settings_local.py.example | 5 +++++ pod/main/settings.py | 2 ++ pod/video/models.py | 13 +++++++------ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pod/custom/settings_local.py.example b/pod/custom/settings_local.py.example index b59d992ae6..1dfe8e89e7 100644 --- a/pod/custom/settings_local.py.example +++ b/pod/custom/settings_local.py.example @@ -1361,3 +1361,8 @@ COOKIE_LEARN_MORE = "" # 'TRACKING_TEMPLATE': 'custom/tracking.html', """ USE_VIDEO_EVENT_TRACKING = False + +""" +# Durée (en nombre de jours) sur laquelle on souhaite compter le nombre de vues récentes +""" +VIDEO_RECENT_VIEWCOUNT = 180 \ No newline at end of file diff --git a/pod/main/settings.py b/pod/main/settings.py index 69b978f03e..c470a4cb42 100644 --- a/pod/main/settings.py +++ b/pod/main/settings.py @@ -309,3 +309,5 @@ # Hide types filter on sidebar HIDE_TYPES = False """ + +VIDEO_RECENT_VIEWCOUNT = 180 diff --git a/pod/video/models.py b/pod/video/models.py index 92bc485dd9..41b778da46 100755 --- a/pod/video/models.py +++ b/pod/video/models.py @@ -9,8 +9,6 @@ import hashlib import datetime -from django.utils import timezone - from django.db import models from django.conf import settings from django.utils.translation import ugettext_lazy as _ @@ -59,7 +57,9 @@ RESTRICT_EDIT_VIDEO_ACCESS_TO_STAFF_ONLY = getattr( settings, "RESTRICT_EDIT_VIDEO_ACCESS_TO_STAFF_ONLY", False ) - +VIDEO_RECENT_VIEWCOUNT = getattr( + settings, "VIDEO_RECENT_VIEWCOUNT", 180 +) VIDEOS_DIR = getattr(settings, "VIDEOS_DIR", "videos") LANG_CHOICES = getattr( @@ -865,7 +865,7 @@ def viewcount(self): @property def recentViewcount(self): """Get the view counter of a video.""" - return self.get_viewcount(180) + return self.get_viewcount(VIDEO_RECENT_VIEWCOUNT) recentViewcount.fget.short_description = _("Sum of view of last 6 months (180 days)") @@ -998,10 +998,11 @@ def get_default_version_link(self, slug_private): def get_viewcount(self, from_nb_day=0): """Get the view counter of a video.""" - set = self.viewcount_set.all(); - if from_nb_day != 0 : + if from_nb_day > 0 : d = datetime.date.today() - timezone.timedelta(days=from_nb_day); set = self.viewcount_set.filter(date__gte=d); + else : + set = self.viewcount_set.all(); count_sum = set.aggregate(Sum("count")) From cc71cbc562f2ac1bfa3f72f25835aefbd3b0e5cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9line=20Didier?= Date: Fri, 10 Jun 2022 17:31:58 +0200 Subject: [PATCH 30/45] Correction PR2 --- pod/video/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pod/video/models.py b/pod/video/models.py index 41b778da46..d0fc944eae 100755 --- a/pod/video/models.py +++ b/pod/video/models.py @@ -999,10 +999,10 @@ def get_default_version_link(self, slug_private): def get_viewcount(self, from_nb_day=0): """Get the view counter of a video.""" if from_nb_day > 0 : - d = datetime.date.today() - timezone.timedelta(days=from_nb_day); - set = self.viewcount_set.filter(date__gte=d); + d = datetime.date.today() - timezone.timedelta(days=from_nb_day) + set = self.viewcount_set.filter(date__gte=d) else : - set = self.viewcount_set.all(); + set = self.viewcount_set.all() count_sum = set.aggregate(Sum("count")) From 712f13a56304eb12fb7f899148c6b09f6cca8cc2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 13 Jun 2022 09:36:52 +0000 Subject: [PATCH 31/45] Fixup: format code with Black --- pod/video/models.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pod/video/models.py b/pod/video/models.py index d0fc944eae..8e1894f1b0 100755 --- a/pod/video/models.py +++ b/pod/video/models.py @@ -57,9 +57,7 @@ RESTRICT_EDIT_VIDEO_ACCESS_TO_STAFF_ONLY = getattr( settings, "RESTRICT_EDIT_VIDEO_ACCESS_TO_STAFF_ONLY", False ) -VIDEO_RECENT_VIEWCOUNT = getattr( - settings, "VIDEO_RECENT_VIEWCOUNT", 180 -) +VIDEO_RECENT_VIEWCOUNT = getattr(settings, "VIDEO_RECENT_VIEWCOUNT", 180) VIDEOS_DIR = getattr(settings, "VIDEOS_DIR", "videos") LANG_CHOICES = getattr( @@ -998,10 +996,10 @@ def get_default_version_link(self, slug_private): def get_viewcount(self, from_nb_day=0): """Get the view counter of a video.""" - if from_nb_day > 0 : + if from_nb_day > 0: d = datetime.date.today() - timezone.timedelta(days=from_nb_day) set = self.viewcount_set.filter(date__gte=d) - else : + else: set = self.viewcount_set.all() count_sum = set.aggregate(Sum("count")) From 45719617052328067c160bd10927823375bcc613 Mon Sep 17 00:00:00 2001 From: mattbild Date: Tue, 14 Jun 2022 08:36:54 +0200 Subject: [PATCH 32/45] feature liveevent --- .coveragerc | 1 + .github/workflows/pod.yml | 1 + pod/custom/settings_local.py.example | 54 +- pod/live/admin.py | 171 +- pod/live/forms.py | 260 ++- .../management/commands/checkLiveStartStop.py | 104 ++ pod/live/models.py | 418 ++++- pod/live/pilotingInterface.py | 346 ++++ pod/live/rest_urls.py | 3 +- pod/live/rest_views.py | 49 +- pod/live/static/css/event.css | 3 + pod/live/static/img/default-event.svg | 36 + .../static/js/broadcaster_from_building.js | 95 + pod/live/static/js/viewcounter.js | 2 +- .../templates/live/{live.html => direct.html} | 69 +- .../live/{building.html => directs.html} | 6 +- .../live/{lives.html => directs_all.html} | 4 +- pod/live/templates/live/event-all-info.html | 41 + .../live/{live-form.html => event-form.html} | 4 +- pod/live/templates/live/event-iframe.html | 208 +++ pod/live/templates/live/event-info.html | 111 ++ pod/live/templates/live/event.html | 539 ++++++ pod/live/templates/live/event_card.html | 67 + pod/live/templates/live/event_delete.html | 78 + pod/live/templates/live/event_edit.html | 218 +++ pod/live/templates/live/event_videos.html | 8 + pod/live/templates/live/events.html | 57 + pod/live/templates/live/events_list.html | 44 + pod/live/templates/live/events_next.html | 18 + pod/live/templates/live/filter_aside.html | 52 + pod/live/templates/live/my_events.html | 75 + pod/live/templatetags/event_tags.py | 30 + pod/live/tests/test_models.py | 196 +- pod/live/tests/test_utils.py | 56 + pod/live/tests/test_views.py | 892 +++++++++- pod/live/urls.py | 90 +- pod/live/utils.py | 169 ++ pod/live/views.py | 882 ++++++++- pod/locale/fr/LC_MESSAGES/django.mo | Bin 111983 -> 119948 bytes pod/locale/fr/LC_MESSAGES/django.po | 1582 +++++++++++------ pod/locale/fr/LC_MESSAGES/djangojs.mo | Bin 11133 -> 11517 bytes pod/locale/fr/LC_MESSAGES/djangojs.po | 555 +++++- pod/locale/nl/LC_MESSAGES/django.po | 1471 +++++++++------ pod/locale/nl/LC_MESSAGES/djangojs.po | 539 +++++- pod/main/context_processors.py | 9 +- pod/main/settings.py | 7 + pod/main/static/css/iframe.css | 17 + pod/main/templates/base.html | 8 +- pod/main/templates/navbar.html | 5 +- pod/main/test_settings.py | 6 +- pod/settings.py | 3 + pod/video/encode.py | 17 +- pod/video/utils.py | 2 +- 53 files changed, 8264 insertions(+), 1414 deletions(-) create mode 100644 pod/live/management/commands/checkLiveStartStop.py create mode 100644 pod/live/pilotingInterface.py create mode 100644 pod/live/static/css/event.css create mode 100644 pod/live/static/img/default-event.svg create mode 100644 pod/live/static/js/broadcaster_from_building.js rename pod/live/templates/live/{live.html => direct.html} (88%) rename pod/live/templates/live/{building.html => directs.html} (77%) rename pod/live/templates/live/{lives.html => directs_all.html} (82%) create mode 100644 pod/live/templates/live/event-all-info.html rename pod/live/templates/live/{live-form.html => event-form.html} (86%) create mode 100644 pod/live/templates/live/event-iframe.html create mode 100644 pod/live/templates/live/event-info.html create mode 100644 pod/live/templates/live/event.html create mode 100644 pod/live/templates/live/event_card.html create mode 100644 pod/live/templates/live/event_delete.html create mode 100644 pod/live/templates/live/event_edit.html create mode 100644 pod/live/templates/live/event_videos.html create mode 100644 pod/live/templates/live/events.html create mode 100644 pod/live/templates/live/events_list.html create mode 100644 pod/live/templates/live/events_next.html create mode 100644 pod/live/templates/live/filter_aside.html create mode 100644 pod/live/templates/live/my_events.html create mode 100644 pod/live/templatetags/event_tags.py create mode 100644 pod/live/tests/test_utils.py create mode 100644 pod/live/utils.py diff --git a/.coveragerc b/.coveragerc index d09282673e..0b95328c72 100755 --- a/.coveragerc +++ b/.coveragerc @@ -16,3 +16,4 @@ omit = pod/*settings*.py pod/*/forms.py pod/video/bbb.py scripts/bbb-pod-live/*.* + pod/live/pilotingInterface.py diff --git a/.github/workflows/pod.yml b/.github/workflows/pod.yml index b7f3698786..9215611da0 100644 --- a/.github/workflows/pod.yml +++ b/.github/workflows/pod.yml @@ -45,6 +45,7 @@ jobs: pip install -r requirements.txt pip install flake8 pip install coveralls + pip install httmock - name: Flake8 compliance run: | diff --git a/pod/custom/settings_local.py.example b/pod/custom/settings_local.py.example index 1dfe8e89e7..20e957190b 100644 --- a/pod/custom/settings_local.py.example +++ b/pod/custom/settings_local.py.example @@ -996,6 +996,11 @@ CREATE_GROUP_FROM_GROUPS = False """ AFFILIATION_STAFF = ('faculty', 'employee', 'staff') +""" +# Groupes ou affiliations des personnes autorisées à créer un évènement +""" +AFFILIATION_EVENT = ['faculty', 'employee', 'staff'] + """ # Valeurs possibles pour l'Affiliation du compte """ @@ -1170,11 +1175,6 @@ DS_PARAM = { } } -""" -# La liste des utilisateurs regardant le direct sera réservée au staff -""" -VIEWERS_ONLY_FOR_STAFF = False - """ # Temps (en seconde) entre deux envois d'un signal au serveur, # pour signaler la présence sur un live @@ -1362,6 +1362,50 @@ COOKIE_LEARN_MORE = "" """ USE_VIDEO_EVENT_TRACKING = False +""" +# Affiche ou non les prochains évènements sur la page d'accueil +""" +SHOW_EVENTS_ON_HOMEPAGE = False + +""" +# Chemin racine du répertoire où sont déposés temporairement les +# enregistrements des évènements éffectués depuis POD pour +# converstion en ressource vidéo (VOD) +""" +DEFAULT_EVENT_PATH = "" + +""" +# Types de logiciel de serveur de streaming utilisés. +# Actuellement disponible Wowza. +# Si vous utilisez une autre logiciel, il faut développer une interface dans pod/live/pilotingInterface.py +""" +BROADCASTER_PILOTING_SOFTWARE = ['Wowza',] + +""" +# Image par défaut affichée comme poster ou vignette, +# utilisée pour présenter l'évènement'. +# Cette image doit se situer dans le répertoire static. +""" +DEFAULT_EVENT_THUMBNAIL = "/img/default-event.svg" + +""" +# Type par défaut affecté à un évènement direct +# (en général, le type ayant pour identifiant '1' est 'Other') +""" +DEFAULT_EVENT_TYPE_ID = 1 + + +""" +# Si True, un courriel est envoyé aux managers +# et à l'auteur (si DEBUG est à False) à la création/modification d'un event +""" +EMAIL_ON_EVENT_SCHEDULING = True + +""" +# Si True, un courriel est également envoyé à l'admin (si DEBUG est à False) +# à la création/modification d'un event +""" +EMAIL_ADMIN_ON_EVENT_SCHEDULING = True """ # Durée (en nombre de jours) sur laquelle on souhaite compter le nombre de vues récentes """ diff --git a/pod/live/admin.py b/pod/live/admin.py index c7220681d1..f3ad89aac3 100644 --- a/pod/live/admin.py +++ b/pod/live/admin.py @@ -1,16 +1,25 @@ +from django.conf import settings from django.contrib import admin - -from .models import Building -from .models import Broadcaster -from .models import HeartBeat from django.contrib.sites.models import Site -from pod.live.forms import BuildingAdminForm -from .forms import BroadcasterAdminForm from django.contrib.sites.shortcuts import get_current_site -from pod.video.models import Video +from django.forms import Textarea +from django.utils.html import format_html +from django.utils.translation import ugettext_lazy as _ +from js_asset import static + +from pod.live.forms import BuildingAdminForm, EventAdminForm, BroadcasterAdminForm +from pod.live.models import Building, Event, Broadcaster, HeartBeat, Video + +DEFAULT_EVENT_THUMBNAIL = getattr( + settings, "DEFAULT_EVENT_THUMBNAIL", "img/default-event.svg" +) # Register your models here. +FILEPICKER = False +if getattr(settings, "USE_PODFILE", False): + FILEPICKER = True + class HeartBeatAdmin(admin.ModelAdmin): list_display = ("viewkey", "user", "broadcaster", "last_heartbeat") @@ -64,10 +73,22 @@ class BroadcasterAdmin(admin.ModelAdmin): "building", "url", "status", + "is_recording_admin", "is_restricted", + "piloting_conf", ) readonly_fields = ["slug"] + def get_form(self, request, obj=None, **kwargs): + kwargs["widgets"] = { + "piloting_conf": Textarea( + attrs={ + "placeholder": "{\n 'server_url':'...',\n 'application':'...',\n 'livestream':'...',\n}" + } + ) + } + return super().get_form(request, obj, **kwargs) + def get_queryset(self, request): qs = super().get_queryset(request) if not request.user.is_superuser: @@ -97,6 +118,142 @@ class Media: ) +class PasswordFilter(admin.SimpleListFilter): + title = _("password") + parameter_name = "password" + + def lookups(self, request, model_admin): + return ( + ("Yes", _("Yes")), + ("No", _("No")), + ) + + def queryset(self, request, queryset): + value = self.value() + if value == "No": + queryset = queryset.exclude( + pk__in=[event.id for event in queryset if event.password] + ) + elif value == "Yes": + queryset = queryset.exclude( + pk__in=[event.id for event in queryset if not event.password] + ) + return queryset + + +class EventAdmin(admin.ModelAdmin): + def get_form(self, request, obj=None, **kwargs): + ModelForm = super(EventAdmin, self).get_form(request, obj, **kwargs) + + class ModelFormMetaClass(ModelForm): + def __new__(cls, *args, **kwargs): + kwargs["request"] = request + return ModelForm(*args, **kwargs) + + return ModelFormMetaClass + + def get_broadcaster_admin(self, instance): + return instance.broadcaster.name + + get_broadcaster_admin.short_description = _("Broadcaster") + + def is_auto_start_admin(self, instance): + return instance.is_auto_start + + is_auto_start_admin.short_description = _("Auto start admin") + is_auto_start_admin.boolean = True + + def get_thumbnail_admin(self, instance): + if instance.thumbnail and instance.thumbnail.file_exist(): + im = instance.get_thumbnail( + instance.thumbnail.file, "100x100", crop="center", quality=72 + ) + thumbnail_url = im.url + else: + thumbnail_url = static(DEFAULT_EVENT_THUMBNAIL) + return format_html( + '%s' + % ( + thumbnail_url, + instance.title.replace("{", "").replace("}", "").replace('"', "'"), + ) + ) + + get_thumbnail_admin.short_description = _("Thumbnails") + get_thumbnail_admin.list_filter = True + + form = EventAdminForm + list_display = [ + "title", + "owner", + "start_date", + "start_time", + "end_time", + "get_broadcaster_admin", + "is_draft", + "is_restricted", + "password", + "is_auto_start_admin", + "get_thumbnail_admin", + ] + fields = [ + "title", + "description", + "owner", + "additional_owners", + "start_date", + "start_time", + "end_time", + "type", + "broadcaster", + "iframe_url", + "iframe_height", + "aside_iframe_url", + "is_draft", + "password", + "is_restricted", + "restrict_access_to_groups", + "is_auto_start", + ] + search_fields = [ + "title", + "broadcaster__name", + ] + + list_filter = [ + "start_date", + "is_draft", + "is_restricted", + "is_auto_start", + PasswordFilter, + ("broadcaster__building", admin.RelatedOnlyFieldListFilter), + ] + + get_broadcaster_admin.admin_order_field = "broadcaster" + is_auto_start_admin.admin_order_field = "is_auto_start" + + if FILEPICKER: + fields.append("thumbnail") + + class Media: + css = { + "all": ( + "css/pod.css", + "bootstrap-4/css/bootstrap.min.css", + "bootstrap-4/css/bootstrap-grid.css", + ) + } + js = ( + "podfile/js/filewidget.js", + "js/main.js", + "js/validate-date_delete-field.js", + "feather-icons/feather.min.js", + "bootstrap-4/js/bootstrap.min.js", + ) + + admin.site.register(Building, BuildingAdmin) admin.site.register(Broadcaster, BroadcasterAdmin) admin.site.register(HeartBeat, HeartBeatAdmin) +admin.site.register(Event, EventAdmin) diff --git a/pod/live/forms.py b/pod/live/forms.py index 06170c39fb..b6fc2909ff 100644 --- a/pod/live/forms.py +++ b/pod/live/forms.py @@ -1,15 +1,26 @@ +from datetime import date, datetime + from django import forms from django.conf import settings -from pod.live.models import Building -from pod.live.models import Broadcaster -from pod.main.forms import add_placeholder_and_asterisk +from django.contrib.admin import widgets +from django.db.models import Q from django.utils.translation import ugettext_lazy as _ +from pod.live.models import ( + Broadcaster, + get_building_having_available_broadcaster, + get_available_broadcasters_of_building, +) +from pod.live.models import Building, Event +from pod.main.forms import add_placeholder_and_asterisk + FILEPICKER = False if getattr(settings, "USE_PODFILE", False): FILEPICKER = True from pod.podfile.widgets import CustomFileWidget +PILOTING_CHOICES = getattr(settings, "BROADCASTER_PILOTING_SOFTWARE", []) + class BuildingAdminForm(forms.ModelForm): required_css_class = "required" @@ -38,6 +49,17 @@ def __init__(self, *args, **kwargs): if FILEPICKER: self.fields["poster"].widget = CustomFileWidget(type="image") + impl_choices = [[None, ""]] + for val in PILOTING_CHOICES: + impl_choices.append([val, val]) + + self.fields["piloting_implementation"] = forms.ChoiceField( + choices=impl_choices, + required=False, + label=_("Piloting implementation"), + help_text=_("Select the piloting implementation for to this broadcaster."), + ) + def clean(self): super(BroadcasterAdminForm, self).clean() @@ -46,9 +68,237 @@ class Meta(object): fields = "__all__" -class LivePasswordForm(forms.Form): +class EventAdminForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + self.request = kwargs.pop("request", None) + super(EventAdminForm, self).__init__(*args, **kwargs) + self.fields["owner"].initial = self.request.user + if FILEPICKER and self.fields.get("thumbnail"): + self.fields["thumbnail"].widget = CustomFileWidget(type="image") + + def clean(self): + super(EventAdminForm, self).clean() + check_event_date_and_hour(self) + + class Meta(object): + model = Event + fields = "__all__" + widgets = { + "start_time": forms.TimeInput(format="%H:%M"), + "end_time": forms.TimeInput(format="%H:%M"), + } + + +class CustomBroadcasterChoiceField(forms.ModelChoiceField): + def label_from_instance(self, obj): + return obj.name + + +def check_event_date_and_hour(form): + if ( + not {"start_time", "start_time", "end_time", "broadcaster"} + <= form.cleaned_data.keys() + ): + return + + d_deb = form.cleaned_data["start_date"] + h_deb = form.cleaned_data["start_time"] + h_fin = form.cleaned_data["end_time"] + brd = form.cleaned_data["broadcaster"] + + if d_deb == date.today() and datetime.now().time() >= h_fin: + form.add_error("end_time", _("End should not be in the past")) + raise forms.ValidationError(_("An event cannot be planned in the past")) + + if h_deb >= h_fin: + form.add_error("start_time", _("Start should not be after end")) + form.add_error("end_time", _("Start should not be after end")) + raise forms.ValidationError("Date error.") + + events = Event.objects.filter( + Q(broadcaster_id=brd.id) + & Q(start_date=d_deb) + & ( + (Q(start_time__lte=h_deb) & Q(end_time__gte=h_fin)) + | (Q(start_time__gte=h_deb) & Q(end_time__lte=h_fin)) + | (Q(start_time__lte=h_deb) & Q(end_time__gt=h_deb)) + | (Q(start_time__lt=h_fin) & Q(end_time__gte=h_fin)) + ) + ) + if form.instance.id: + events = events.exclude(id=form.instance.id) + + if events.exists(): + form.add_error("start_date", _("An event is already planned at these dates")) + raise forms.ValidationError("Date error.") + + +class EventPasswordForm(forms.Form): password = forms.CharField(label=_("Password"), widget=forms.PasswordInput()) def __init__(self, *args, **kwargs): - super(LivePasswordForm, self).__init__(*args, **kwargs) + super(EventPasswordForm, self).__init__(*args, **kwargs) + self.fields = add_placeholder_and_asterisk(self.fields) + + +class EventForm(forms.ModelForm): + + building = forms.ModelChoiceField( + label=_("Building"), + queryset=Building.objects.none(), + to_field_name="name", + empty_label=None, + ) + + broadcaster = CustomBroadcasterChoiceField( + label=_("Broadcaster device"), + queryset=Broadcaster.objects.none(), + empty_label=None, + ) + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop("user", None) + is_current_event = kwargs.pop("is_current_event", None) + broadcaster_id = kwargs.pop("broadcaster_id", None) + building_id = kwargs.pop("building_id", None) + super(EventForm, self).__init__(*args, **kwargs) + self.auto_id = "event_%s" + self.fields["owner"].initial = self.user + # Manage required fields html + self.fields = add_placeholder_and_asterisk(self.fields) + # Manage fields to display + self.initFields(is_current_event) + + # mise a jour dynamique de la liste des diffuseurs + if "building" in self.data: + self.saving() + return + + if self.instance.pk and not is_current_event: + self.editing() + return + + if not self.instance.pk: + # à la création + self.creating(broadcaster_id, building_id) + + def creating(self, broadcaster_id, building_id): + if broadcaster_id is not None and building_id is not None: + query_buildings = get_building_having_available_broadcaster( + self.user, building_id + ) + self.fields["building"].queryset = query_buildings.all() + self.initial["building"] = ( + Building.objects.filter(Q(id=building_id)).first().name + ) + query_broadcaster = get_available_broadcasters_of_building( + self.user, building_id, broadcaster_id + ) + self.fields["broadcaster"].queryset = query_broadcaster.all() + self.initial["broadcaster"] = broadcaster_id + else: + query_buildings = get_building_having_available_broadcaster(self.user) + if query_buildings: + self.fields["building"].queryset = query_buildings.all() + self.initial["building"] = query_buildings.first().name + self.fields[ + "broadcaster" + ].queryset = get_available_broadcasters_of_building( + self.user, query_buildings.first() + ) + + def editing(self): + broadcaster = self.instance.broadcaster + self.fields["broadcaster"].queryset = get_available_broadcasters_of_building( + self.user, broadcaster.building.id, broadcaster.id + ) + self.fields["building"].queryset = get_building_having_available_broadcaster( + self.user, broadcaster.building.id + ) + self.initial["building"] = broadcaster.building.name + + def saving(self): + try: + build = Building.objects.filter(name=self.data.get("building")).first() + self.fields["broadcaster"].queryset = get_available_broadcasters_of_building( + self.user, build.id + ) + self.fields["building"].queryset = get_building_having_available_broadcaster( + self.user, build.id + ) + self.initial["building"] = build.name + except (ValueError, TypeError): + pass # invalid input from the client; ignore and fallback to empty Broadcaster queryset + + def initFields(self, is_current_event): + if not self.user.is_superuser: + self.remove_field("owner") + self.instance.owner = self.user + if is_current_event: + self.remove_field("start_date") + self.remove_field("start_time") + self.remove_field("is_draft") + self.remove_field("is_auto_start") + self.remove_field("is_restricted") + self.remove_field("type") + self.remove_field("description") + self.remove_field("building") + self.remove_field("broadcaster") + self.remove_field("owner") + self.remove_field("thumbnail") + + def remove_field(self, field): + if self.fields.get(field): + del self.fields[field] + + def clean(self): + check_event_date_and_hour(self) + cleaned_data = super(EventForm, self).clean() + + if cleaned_data["is_draft"]: + cleaned_data["password"] = None + cleaned_data["is_restricted"] = False + cleaned_data["restrict_access_to_groups"] = [] + + class Meta(object): + model = Event + fields = [ + "title", + "description", + "owner", + "additional_owners", + "start_date", + "start_time", + "end_time", + "building", + "broadcaster", + "type", + "is_draft", + "password", + "is_restricted", + "restrict_access_to_groups", + "is_auto_start", + "iframe_url", + "iframe_height", + "aside_iframe_url", + ] + widgets = { + "start_date": widgets.AdminDateWidget, + "start_time": forms.TimeInput(format="%H:%M", attrs={"class": "vTimeField"}), + "end_time": forms.TimeInput(format="%H:%M", attrs={"class": "vTimeField"}), + } + if FILEPICKER: + fields.append("thumbnail") + widgets["thumbnail"] = CustomFileWidget(type="image") + + +class EventDeleteForm(forms.Form): + agree = forms.BooleanField( + label=_("I agree"), + help_text=_("Delete event cannot be undo"), + widget=forms.CheckboxInput(), + ) + + def __init__(self, *args, **kwargs): + super(EventDeleteForm, self).__init__(*args, **kwargs) self.fields = add_placeholder_and_asterisk(self.fields) diff --git a/pod/live/management/commands/checkLiveStartStop.py b/pod/live/management/commands/checkLiveStartStop.py new file mode 100644 index 0000000000..d6354fc1a0 --- /dev/null +++ b/pod/live/management/commands/checkLiveStartStop.py @@ -0,0 +1,104 @@ +import json +from datetime import date, datetime + +from django.conf import settings +from django.core.management.base import BaseCommand +from django.db.models import Q + +from pod.live.models import Event +from pod.live.views import ( + is_recording, + event_stoprecord, + event_startrecord, +) + +DEFAULT_EVENT_PATH = getattr(settings, "DEFAULT_EVENT_PATH", "") +DEBUG = getattr(settings, "DEBUG", "") + + +class Command(BaseCommand): + help = "start or stop broadcaster recording based on live events " + + debug_mode = DEBUG + + def add_arguments(self, parser): + parser.add_argument( + "-f", + "--force", + action="store_true", + help="Start and stop recording FOR REAL", + ) + + def handle(self, *args, **options): + + if options["force"]: + self.debug_mode = False + + self.stdout.write( + f"- Beginning at {datetime.now().strftime('%H:%M:%S')}", ending="" + ) + self.stdout.write(" - IN DEBUG MODE -" if self.debug_mode else "") + + self.stop_finished() + + self.start_new() + + self.stdout.write("- End -") + + def stop_finished(self): + self.stdout.write("-- Stopping finished events (if started with Pod) :") + + now = datetime.now().replace(second=0, microsecond=0) + + # events ending now + events = Event.objects.filter(Q(start_date=date.today()) & Q(end_time=now)) + + for event in events: + if not is_recording(event.broadcaster, True): + continue + + self.stdout.write( + f"Broadcaster {event.broadcaster.name} should be stopped : ", ending="" + ) + + if self.debug_mode: + self.stdout.write("... but not tried (debug mode) ") + continue + + response = event_stoprecord(event.id, event.broadcaster.id) + if json.loads(response.content)["success"]: + self.stdout.write(" ... stopped ") + else: + self.stderr.write(" ... fail to stop recording") + + def start_new(self): + + self.stdout.write("-- Starting new events :") + + events = Event.objects.filter( + Q(is_auto_start=True) + & Q(start_date=date.today()) + & Q(start_time__lte=datetime.now()) + & Q(end_time__gt=datetime.now()) + ) + + for event in events: + + if is_recording(event.broadcaster): + self.stdout.write( + f"Broadcaster {event.broadcaster.name} is already recording" + ) + continue + + self.stdout.write( + f"Broadcaster {event.broadcaster.name} should be started : ", ending="" + ) + + if self.debug_mode: + self.stdout.write("... but not tried (debug mode) ") + continue + + if event_startrecord(event.id, event.broadcaster.id): + self.stdout.write(" ... successfully started") + else: + self.stderr.write(" ... fail to start") diff --git a/pod/live/models.py b/pod/live/models.py index cc2ad564a2..298b308183 100644 --- a/pod/live/models.py +++ b/pod/live/models.py @@ -1,19 +1,30 @@ """Esup-Pod "live" models.""" +import hashlib +from datetime import date, datetime -from django.db import models -from django.conf import settings -from django.utils.translation import ugettext_lazy as _ from ckeditor.fields import RichTextField -from django.template.defaultfilters import slugify -from pod.video.models import Video +from django.conf import settings +from django.contrib.auth.models import Group +from django.contrib.auth.models import User from django.contrib.sites.models import Site -from select2 import fields as select2_fields -from django.dispatch import receiver +from django.contrib.sites.shortcuts import get_current_site +from django.contrib.staticfiles.templatetags.staticfiles import static +from django.core.exceptions import ValidationError +from django.db import models +from django.db.models import Q from django.db.models.signals import post_save +from django.dispatch import receiver +from django.template.defaultfilters import slugify from django.urls import reverse -from django.contrib.auth.models import User from django.utils import timezone +from django.utils.html import format_html +from django.utils.translation import ugettext_lazy as _ +from select2 import fields as select2_fields +from sorl.thumbnail import get_thumbnail +from pod.authentication.models import AccessGroup +from pod.main.models import get_nextautoincrement +from pod.video.models import Video, Type if getattr(settings, "USE_PODFILE", False): from pod.podfile.models import CustomImageModel @@ -24,6 +35,14 @@ from pod.main.models import CustomImageModel DEFAULT_THUMBNAIL = getattr(settings, "DEFAULT_THUMBNAIL", "img/default.svg") +DEFAULT_EVENT_THUMBNAIL = getattr( + settings, "DEFAULT_EVENT_THUMBNAIL", "img/default-event.svg" +) +DEFAULT_EVENT_TYPE_ID = getattr(settings, "DEFAULT_EVENT_TYPE_ID", 1) +AFFILIATION_EVENT = getattr( + settings, "AFFILIATION_EVENT", ("faculty", "employee", "staff") +) +SECRET_KEY = getattr(settings, "SECRET_KEY", "") class Building(models.Model): @@ -52,12 +71,7 @@ class Meta: verbose_name = _("Building") verbose_name_plural = _("Buildings") ordering = ["name"] - permissions = ( - ( - "view_building_supervisor", - "Can see the supervisor page for building", - ), - ) + permissions = (("acces_live_pages", "Access to all live pages"),) @receiver(post_save, sender=Building) @@ -66,6 +80,40 @@ def default_site_building(sender, instance, created, **kwargs): instance.sites.add(Site.objects.get_current()) +def get_available_broadcasters_of_building(user, building_id, broadcaster_id=None): + right_filter = Broadcaster.objects.filter( + Q(status=True) + & Q(building_id=building_id) + & (Q(manage_groups__isnull=True) | Q(manage_groups__in=user.groups.all())) + ) + if broadcaster_id: + return ( + (right_filter | Broadcaster.objects.filter(Q(id=broadcaster_id))) + .distinct() + .order_by("name") + ) + + return right_filter.distinct().order_by("name") + + +def get_building_having_available_broadcaster(user, building_id=None): + right_filter = Building.objects.filter( + Q(broadcaster__status=True) + & ( + Q(broadcaster__manage_groups__isnull=True) + | Q(broadcaster__manage_groups__in=user.groups.all()) + ) + ) + if building_id: + return ( + (right_filter | Building.objects.filter(Q(id=building_id))) + .distinct() + .order_by("name") + ) + + return right_filter.distinct().order_by("name") + + class Broadcaster(models.Model): name = models.CharField(_("name"), max_length=200, unique=True) slug = models.SlugField( @@ -96,24 +144,6 @@ class Broadcaster(models.Model): null=True, verbose_name=_("Video on hold"), ) - iframe_url = models.URLField( - _("Embedded Site URL"), - help_text=_("Url of the embedded site to display"), - null=True, - blank=True, - ) - iframe_height = models.IntegerField( - _("Embedded Site Height"), - null=True, - blank=True, - help_text=_("Height of the embedded site (in pixels)"), - ) - aside_iframe_url = models.URLField( - _("Embedded aside Site URL"), - help_text=_("Url of the embedded site to display on aside"), - null=True, - blank=True, - ) status = models.BooleanField( default=0, help_text=_("Check if the broadcaster is currently sending stream."), @@ -133,18 +163,36 @@ class Broadcaster(models.Model): help_text=_("Live is accessible from the Live tab"), default=True, ) - password = models.CharField( - _("password"), - help_text=_("Viewing this live will not be possible without this password."), - max_length=50, + viewcount = models.IntegerField(_("Number of viewers"), default=0, editable=False) + viewers = models.ManyToManyField(User, editable=False) + + manage_groups = select2_fields.ManyToManyField( + Group, + blank=True, + verbose_name=_("Groups"), + help_text=_( + "Select one or more groups who can manage event to this broadcaster." + ), + related_name="managegroups", + ) + + piloting_implementation = models.CharField( + max_length=100, blank=True, null=True, + verbose_name=_("Piloting implementation"), + help_text=_("Select the piloting implementation for to this broadcaster."), + ) + + piloting_conf = models.TextField( + null=True, + blank=True, + verbose_name=_("Piloting configuration parameters"), + help_text=_("Add piloting configuration parameters in Json format."), ) - viewcount = models.IntegerField(_("Number of viewers"), default=0, editable=False) - viewers = models.ManyToManyField(User, editable=False) def get_absolute_url(self): - return reverse("live:video_live", args=[str(self.slug)]) + return reverse("live:direct", args=[str(self.slug)]) def __str__(self): return "%s - %s" % (self.name, self.url) @@ -169,6 +217,32 @@ class Meta: def sites(self): return self.building.sites + def check_recording(self): + from pod.live.pilotingInterface import get_piloting_implementation + + impl = get_piloting_implementation(self) + if impl: + return impl.is_recording() + else: + return False + + def is_recording(self): + try: + return self.check_recording() + except Exception: + return False + + def is_recording_admin(self): + try: + if self.check_recording(): + return format_html('Yes') + else: + return format_html('No') + except Exception: + return format_html('Error') + + is_recording_admin.short_description = _("Is recording ?") + class HeartBeat(models.Model): user = models.ForeignKey(User, null=True, verbose_name=_("Viewer")) @@ -182,3 +256,269 @@ class Meta: verbose_name = _("Heartbeat") verbose_name_plural = _("Heartbeats") ordering = ["broadcaster"] + + +def current_time(): + return datetime.now().replace(second=0, microsecond=0) + + +def one_hour_hence(): + return current_time() + timezone.timedelta(hours=1) + + +def get_default_event_type(): + return DEFAULT_EVENT_TYPE_ID + + +def present_or_future_date(value): + if value < date.today(): + raise ValidationError(_("An event cannot be planned in the past")) + return value + + +def select_event_owner(): + return ( + lambda q: (Q(first_name__icontains=q) | Q(last_name__icontains=q)) + & Q(owner__sites=Site.objects.get_current()) + & Q(owner__accessgroups__code_name__in=AFFILIATION_EVENT) + ) + + +class Event(models.Model): + slug = models.SlugField( + _("Slug"), + unique=True, + max_length=255, + editable=False, + ) + + title = models.CharField( + _("Title"), + max_length=250, + help_text=_( + "Please choose a title as short and accurate as " + "possible, reflecting the main subject / context " + "of the content. (max length: 250 characters)" + ), + ) + + description = RichTextField( + _("Description"), + config_name="complete", + blank=True, + help_text=_( + "In this field you can describe your content, " + "add all needed related information, and " + "format the result using the toolbar." + ), + ) + + owner = select2_fields.ForeignKey( + User, + ajax=True, + verbose_name=_("Owner"), + search_field=select_event_owner(), + on_delete=models.CASCADE, + ) + + additional_owners = select2_fields.ManyToManyField( + User, + blank=True, + ajax=True, + js_options={"width": "off"}, + verbose_name=_("Additional owners"), + search_field=select_event_owner(), + related_name="owners_events", + help_text=_( + "You can add additional owners to the event. They " + "will have the same rights as you except that they " + "can't delete this event." + ), + ) + + start_date = models.DateField( + _("Date of event"), + default=date.today, + help_text=_("Start date of the live."), + validators=[present_or_future_date], + ) + start_time = models.TimeField( + _("Start time"), + default=current_time, + help_text=_("Start time of the live event."), + ) + end_time = models.TimeField( + _("End time"), + default=one_hour_hence, + help_text=_("End time of the live event."), + ) + + broadcaster = models.ForeignKey( + Broadcaster, + verbose_name=_("Broadcaster"), + help_text=_("Broadcaster name."), + ) + + type = models.ForeignKey(Type, default=DEFAULT_EVENT_TYPE_ID, verbose_name=_("Type")) + + iframe_url = models.URLField( + _("Embedded Site URL"), + help_text=_("Url of the embedded site to display"), + null=True, + blank=True, + ) + iframe_height = models.IntegerField( + _("Embedded Site Height"), + null=True, + blank=True, + help_text=_("Height of the embedded site (in pixels)"), + ) + aside_iframe_url = models.URLField( + _("Embedded aside Site URL"), + help_text=_("Url of the embedded site to display on aside"), + null=True, + blank=True, + ) + + is_draft = models.BooleanField( + verbose_name=_("Draft"), + help_text=_( + "If this box is checked, the event will be visible " + "only by you and the additional owners " + "but accessible to anyone having the url link." + ), + default=True, + ) + is_restricted = models.BooleanField( + verbose_name=_("Restricted access"), + help_text=_( + "If this box is checked, " + "the event will only be accessible to authenticated users." + ), + default=False, + ) + + restrict_access_to_groups = select2_fields.ManyToManyField( + AccessGroup, + blank=True, + verbose_name=_("Groups"), + help_text=_("Select one or more groups who can access to this event"), + ) + + is_auto_start = models.BooleanField( + verbose_name=_("Auto start"), + help_text=_("If this box is checked, " "the record will start automatically."), + default=False, + ) + + thumbnail = models.ForeignKey( + CustomImageModel, + models.SET_NULL, + blank=True, + null=True, + verbose_name=_("Thumbnails"), + ) + + password = models.CharField( + _("password"), + help_text=_("Viewing this event will not be possible without this password."), + max_length=50, + blank=True, + null=True, + ) + + videos = models.ManyToManyField( + Video, + editable=False, + ) + + class Meta: + verbose_name = _("Event") + verbose_name_plural = _("Events") + ordering = ["start_date", "start_time"] + + def save(self, *args, **kwargs): + if not self.id: + try: + new_id = get_nextautoincrement(Event) + except Exception: + try: + new_id = Event.objects.latest("id").id + new_id += 1 + except Exception: + new_id = 1 + else: + new_id = self.id + new_id = "%04d" % new_id + self.slug = "%s-%s" % (new_id, slugify(self.title)) + super(Event, self).save(*args, **kwargs) + + def __str__(self): + if self.id: + return "%s - %s" % ("%04d" % self.id, self.title) + else: + return "None" + + def get_absolute_url(self): + return reverse("live:event", args=[str(self.slug)]) + + def get_full_url(self, request=None): + """Get the video full URL.""" + full_url = "".join( + ["//", get_current_site(request).domain, self.get_absolute_url()] + ) + return full_url + + def get_hashkey(self): + return hashlib.sha256( + ("%s-%s" % (SECRET_KEY, self.id)).encode("utf-8") + ).hexdigest() + + def get_thumbnail_url(self): + """Get a thumbnail url for the event.""" + request = None + if self.thumbnail and self.thumbnail.file_exist(): + thumbnail_url = "".join( + [ + "//", + get_current_site(request).domain, + self.thumbnail.file.url, + ] + ) + else: + thumbnail_url = static(DEFAULT_EVENT_THUMBNAIL) + return thumbnail_url + + def get_thumbnail_card(self): + """Return thumbnail image card of current event.""" + if self.thumbnail and self.thumbnail.file_exist(): + im = get_thumbnail(self.thumbnail.file, "x170", crop="center", quality=72) + thumbnail_url = im.url + else: + thumbnail_url = static(DEFAULT_EVENT_THUMBNAIL) + return ( + '' + % thumbnail_url + ) + + def is_current(self): + return self.start_date == date.today() and ( + self.start_time <= datetime.now().time() <= self.end_time + ) + + def is_past(self): + return self.start_date < date.today() or ( + self.start_date == date.today() and self.end_time < datetime.now().time() + ) + + def is_coming(self): + return self.start_date > date.today() or ( + self.start_date == date.today() and datetime.now().time() < self.start_time + ) + + def get_start(self): + return datetime.combine(self.start_date, self.start_time) + + def get_end(self): + return datetime.combine(self.start_date, self.end_time) diff --git a/pod/live/pilotingInterface.py b/pod/live/pilotingInterface.py new file mode 100644 index 0000000000..309789250c --- /dev/null +++ b/pod/live/pilotingInterface.py @@ -0,0 +1,346 @@ +import http +import json +import logging +import os +import re +from abc import ABC, abstractmethod +from typing import Optional + +import requests +from django.conf import settings + +from .models import Broadcaster + +EXISTING_BROADCASTER_IMPLEMENTATIONS = ["Wowza"] +DEFAULT_EVENT_PATH = getattr(settings, "DEFAULT_EVENT_PATH", "") + +logger = logging.getLogger("pod.live") + + +class PilotingInterface(ABC): + @abstractmethod + def __init__(self, broadcaster: Broadcaster): + """Initialize the PilotingInterface + :param broadcaster: the broadcaster to pilot""" + self.broadcaster = broadcaster + raise NotImplementedError + + @abstractmethod + def check_piloting_conf(self) -> bool: + """Checks the piloting conf value""" + raise NotImplementedError + + @abstractmethod + def is_available_to_record(self) -> bool: + """Checks if the broadcaster is available""" + raise NotImplementedError + + @abstractmethod + def is_recording(self, with_file_check=False) -> bool: + """Checks if the broadcaster is being recorded + :param with_file_check: checks if tmp recording file is present on the filesystem (recording could have been launch from somewhere else) + """ + raise NotImplementedError + + @abstractmethod + def start(self, event_id, login=None) -> bool: + """Start the recording""" + raise NotImplementedError + + @abstractmethod + def split(self) -> bool: + """Split the current record""" + raise NotImplementedError + + @abstractmethod + def stop(self) -> bool: + """Stop the recording""" + raise NotImplementedError + + @abstractmethod + def get_info_current_record(self) -> dict: + """Get info of current record""" + raise NotImplementedError + + +def get_piloting_implementation(broadcaster) -> Optional[PilotingInterface]: + logger.debug("get_piloting_implementation") + piloting_impl = broadcaster.piloting_implementation + if not piloting_impl: + logger.info( + "'piloting_implementation' value is not set for '" + + broadcaster.name + + "' broadcaster." + ) + return None + + if not piloting_impl.lower() in map(str.lower, EXISTING_BROADCASTER_IMPLEMENTATIONS): + logger.warning( + "'piloting_implementation' : " + + piloting_impl + + " is not know for '" + + broadcaster.name + + "' broadcaster. Available piloting_implementations are '" + + "','".join(EXISTING_BROADCASTER_IMPLEMENTATIONS) + + "'" + ) + return None + + if piloting_impl.lower() == "wowza": + logger.debug( + "'piloting_implementation' found : " + + piloting_impl.lower() + + " for '" + + broadcaster.name + + "' broadcaster." + ) + return Wowza(broadcaster) + + logger.warning("->get_piloting_implementation - This should not happen.") + return None + + +def is_recording_launched_by_pod(self) -> bool: + # Récupération du fichier associé à l'enregistrement du broadcaster + current_record_info = self.get_info_current_record() + if not current_record_info.get("currentFile"): + logging.error(" ... impossible to get recording file name") + return False + + filename = current_record_info.get("currentFile") + full_file_name = os.path.join(DEFAULT_EVENT_PATH, filename) + + # Vérification qu'il existe bien pour cette instance ce Pod + if not os.path.exists(full_file_name): + logging.debug(" ... is not on this POD recording filesystem : " + full_file_name) + return False + + return True + + +class Wowza(PilotingInterface): + def __init__(self, broadcaster: Broadcaster): + self.broadcaster = broadcaster + self.url = None + if self.check_piloting_conf(): + conf = json.loads(self.broadcaster.piloting_conf) + self.url = "{server_url}/v2/servers/_defaultServer_/vhosts/_defaultVHost_/applications/{application}".format( + server_url=conf["server_url"], + application=conf["application"], + ) + + def check_piloting_conf(self) -> bool: + logging.debug("Wowza - Check piloting conf") + conf = self.broadcaster.piloting_conf + if not conf: + logging.error( + "'piloting_conf' value is not set for '" + + self.broadcaster.name + + "' broadcaster." + ) + return False + try: + decoded = json.loads(conf) + except Exception: + logging.error( + "'piloting_conf' has not a valid Json format for '" + + self.broadcaster.name + + "' broadcaster." + ) + return False + if not {"server_url", "application", "livestream"} <= decoded.keys(): + logging.error( + "'piloting_conf' format value for '" + + self.broadcaster.name + + "' broadcaster must be like : " + "{'server_url':'...','application':'...','livestream':'...'}" + ) + return False + + logging.debug("->piloting conf OK") + return True + + def is_available_to_record(self) -> bool: + logging.debug("Wowza - Check availability") + json_conf = self.broadcaster.piloting_conf + conf = json.loads(json_conf) + url_state_live_stream_recording = ( + self.url + "/instances/_definst_/incomingstreams/" + conf["livestream"] + ) + + response = requests.get( + url_state_live_stream_recording, + headers={"Accept": "application/json", "Content-Type": "application/json"}, + ) + + if response.status_code == http.HTTPStatus.OK: + if ( + response.json().get("isConnected") is True + and response.json().get("isRecordingSet") is False + ): + return True + + return False + + def is_recording(self, with_file_check=False) -> bool: + logging.debug("Wowza - Check if is being recorded") + json_conf = self.broadcaster.piloting_conf + conf = json.loads(json_conf) + url_state_live_stream_recording = ( + self.url + "/instances/_definst_/incomingstreams/" + conf["livestream"] + ) + + response = requests.get( + url_state_live_stream_recording, + headers={"Accept": "application/json", "Content-Type": "application/json"}, + ) + + if ( + response.status_code != http.HTTPStatus.OK + or not response.json().get("isConnected") + or not response.json().get("isRecordingSet") + ): + return False + + if with_file_check: + return is_recording_launched_by_pod(self) + else: + return True + + def start(self, event_id=None, login=None) -> bool: + logging.debug("Wowza - Start record") + json_conf = self.broadcaster.piloting_conf + conf = json.loads(json_conf) + url_start_record = ( + self.url + "/instances/_definst_/streamrecorders/" + conf["livestream"] + ) + filename = self.broadcaster.slug + if event_id is not None: + filename = str(event_id) + "_" + filename + elif login is not None: + filename = login + "_" + filename + data = { + "instanceName": "", + "fileVersionDelegateName": "", + "serverName": "", + "recorderName": "", + "currentSize": 0, + "segmentSchedule": "", + "startOnKeyFrame": True, + "outputPath": DEFAULT_EVENT_PATH, + "baseFile": filename + "_${RecordingStartTime}_${SegmentNumber}", + "currentFile": "", + "saveFieldList": [""], + "recordData": False, + "applicationName": "", + "moveFirstVideoFrameToZero": False, + "recorderErrorString": "", + "segmentSize": 0, + "defaultRecorder": False, + "splitOnTcDiscontinuity": False, + "version": "", + "segmentDuration": 0, + "recordingStartTime": "", + "fileTemplate": "", + "backBufferTime": 0, + "segmentationType": "", + "currentDuration": 0, + "fileFormat": "", + "recorderState": "", + "option": "", + } + + response = requests.post( + url_start_record, + json=data, + headers={"Accept": "application/json", "Content-Type": "application/json"}, + ) + + if response.status_code == http.HTTPStatus.CREATED: + if response.json().get("success"): + return True + + return False + + def split(self) -> bool: + logging.debug("Wowza - Split record") + json_conf = self.broadcaster.piloting_conf + conf = json.loads(json_conf) + url_split_record = ( + self.url + + "/instances/_definst_/streamrecorders/" + + conf["livestream"] + + "/actions/splitRecording" + ) + response = requests.put( + url_split_record, + headers={"Accept": "application/json", "Content-Type": "application/json"}, + ) + + if response.status_code == http.HTTPStatus.OK: + if response.json().get("success"): + return True + + return False + + def stop(self) -> bool: + logging.debug("Wowza - Stop_record") + json_conf = self.broadcaster.piloting_conf + conf = json.loads(json_conf) + url_stop_record = ( + self.url + + "/instances/_definst_/streamrecorders/" + + conf["livestream"] + + "/actions/stopRecording" + ) + response = requests.put( + url_stop_record, + headers={"Accept": "application/json", "Content-Type": "application/json"}, + ) + + if response.status_code == http.HTTPStatus.OK: + if response.json().get("success"): + return True + + return False + + def get_info_current_record(self): + logging.debug("Wowza - Get info from current record") + json_conf = self.broadcaster.piloting_conf + conf = json.loads(json_conf) + url_state_live_stream_recording = ( + self.url + "/instances/_definst_/streamrecorders/" + conf["livestream"] + ) + + response = requests.get( + url_state_live_stream_recording, + verify=True, + headers={"Accept": "application/json", "Content-Type": "application/json"}, + ) + + if response.status_code != http.HTTPStatus.OK: + return { + "currentFile": "", + "segmentNumber": "", + "outputPath": "", + "segmentDuration": "", + } + + segment_number = "" + current_file = response.json().get("currentFile") + + try: + ending = current_file.split("_")[-1] + if re.match(r"\d+\.", ending): + number = ending.split(".")[0] + if int(number) > 0: + segment_number = number + except Exception: + pass + + return { + "currentFile": current_file, + "segmentNumber": segment_number, + "outputPath": response.json().get("outputPath"), + "segmentDuration": response.json().get("segmentDuration"), + } diff --git a/pod/live/rest_urls.py b/pod/live/rest_urls.py index 8ff016a442..1820ce565e 100644 --- a/pod/live/rest_urls.py +++ b/pod/live/rest_urls.py @@ -1,6 +1,7 @@ -from .rest_views import BuildingViewSet, BroadcasterViewSet +from .rest_views import BuildingViewSet, BroadcasterViewSet, EventViewSet def add_register(router): router.register(r"buildings", BuildingViewSet) router.register(r"broadcasters", BroadcasterViewSet) + router.register(r"events", EventViewSet) diff --git a/pod/live/rest_views.py b/pod/live/rest_views.py index f727ba39f3..23989d7476 100644 --- a/pod/live/rest_views.py +++ b/pod/live/rest_views.py @@ -1,4 +1,4 @@ -from .models import Building, Broadcaster +from .models import Building, Broadcaster, Event from rest_framework import serializers, viewsets # Serializers define the API representation. @@ -11,6 +11,11 @@ class Meta: class BroadcasterSerializer(serializers.HyperlinkedModelSerializer): + url = serializers.HyperlinkedIdentityField( + view_name="broadcaster-detail", lookup_field="slug" + ) + broadcaster_url = serializers.URLField(source="url") + class Meta: model = Broadcaster fields = ( @@ -21,13 +26,48 @@ class Meta: "building", "description", "poster", - "url", + "broadcaster_url", "status", "is_restricted", + "manage_groups", + "piloting_implementation", + "piloting_conf", ) lookup_field = "slug" +class EventSerializer(serializers.HyperlinkedModelSerializer): + broadcaster = serializers.HyperlinkedRelatedField( + view_name="broadcaster-detail", + queryset=Broadcaster.objects.all(), + many=False, + read_only=False, + lookup_field="slug", + ) + + class Meta: + model = Event + fields = ( + "id", + "url", + "title", + "owner", + "additional_owners", + "slug", + "description", + "start_date", + "start_time", + "end_time", + "broadcaster", + "type", + "is_draft", + "is_restricted", + "is_auto_start", + "videos", + "thumbnail", + ) + + ############################################################################# # ViewSets define the view behavior. ############################################################################# @@ -42,3 +82,8 @@ class BroadcasterViewSet(viewsets.ModelViewSet): queryset = Broadcaster.objects.all().order_by("building", "name") serializer_class = BroadcasterSerializer lookup_field = "slug" + + +class EventViewSet(viewsets.ModelViewSet): + queryset = Event.objects.all().order_by("start_date", "start_time") + serializer_class = EventSerializer diff --git a/pod/live/static/css/event.css b/pod/live/static/css/event.css new file mode 100644 index 0000000000..b2db2a8467 --- /dev/null +++ b/pod/live/static/css/event.css @@ -0,0 +1,3 @@ +.current_event_feather{ + stroke: red; +} \ No newline at end of file diff --git a/pod/live/static/img/default-event.svg b/pod/live/static/img/default-event.svg new file mode 100644 index 0000000000..b96e03308c --- /dev/null +++ b/pod/live/static/img/default-event.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + diff --git a/pod/live/static/js/broadcaster_from_building.js b/pod/live/static/js/broadcaster_from_building.js new file mode 100644 index 0000000000..6cf1f80ef7 --- /dev/null +++ b/pod/live/static/js/broadcaster_from_building.js @@ -0,0 +1,95 @@ +$(document).ready(function () { + let broadcastField = $("#event_broadcaster"); + let restrictedCheckBox = $("#event_is_restricted"); + let restrictedHelp = $("#event_is_restrictedHelp"); + let restrictedLabel = $(".field_is_restricted"); + + let change_restriction = (restrict) => { + if (restrict === true) { + restrictedCheckBox.prop("checked", true); + restrictedCheckBox.attr("onclick", "return false"); + restrictedHelp.html( + gettext("Restricted because the broadcaster is restricted") + ); + restrictedLabel.css("opacity", "0.5"); + } else { + restrictedCheckBox.removeAttr("onclick"); + restrictedHelp.html( + gettext( + "If this box is checked, the event will only be accessible to authenticated users." + ) + ); + restrictedLabel.css("opacity", ""); + } + }; + + let getBroadcasterRestriction = () => { + let broadcaster_id = broadcastField.find(":selected").val(); + if (typeof broadcaster_id === "undefined" || broadcaster_id === "") return; + + $.ajax({ + url: "/live/ajax_calls/getbroadcasterrestriction/", + type: "GET", + dataType: "JSON", + cache: false, + data: { + idbroadcaster: broadcaster_id, + }, + success: (s) => { + change_restriction(s.restricted); + }, + error: () => { + change_restriction(false); + alert(gettext("an error occurred on broadcaster fetch ...")); + }, + }); + }; + + // Update broadcasters list after building change + $("#event_building").change(function () { + $.ajax({ + url: "/live/ajax_calls/getbroadcastersfrombuiding/", + type: "GET", + dataType: "JSON", + cache: false, + data: { + building: this.value, + }, + + success: (broadcasters) => { + broadcastField.html(""); + + if (broadcasters.length === 0) { + console.log("no Broadcaster"); + broadcastField.prop("disabled", true); + broadcastField.append( + "" + ); + } else { + broadcastField.prop("disabled", false); + $.each(broadcasters, (key, value) => { + broadcastField.append( + '" + ); + }); + + // Update restriction after list reload + getBroadcasterRestriction(); + } + }, + error: () => { + alert(gettext("an error occurred during broadcasters load ...")); + }, + }); + }); + + // Update restriction after broadcaster change + broadcastField.change(function () { + getBroadcasterRestriction(); + }); + + // Set restriction on load (if needed) + getBroadcasterRestriction(); +}); diff --git a/pod/live/static/js/viewcounter.js b/pod/live/static/js/viewcounter.js index 99bc97f913..2dcafc6d15 100644 --- a/pod/live/static/js/viewcounter.js +++ b/pod/live/static/js/viewcounter.js @@ -17,7 +17,7 @@ $(document).ready(function () { $.ajax({ type: "GET", url: - "/live/ajax_calls/heartbeat?key=" + + "/live/ajax_calls/heartbeat/?key=" + secret + "&liveid=" + $("#livename").data("liveid"), diff --git a/pod/live/templates/live/live.html b/pod/live/templates/live/direct.html similarity index 88% rename from pod/live/templates/live/live.html rename to pod/live/templates/live/direct.html index cfbb7540ba..325faf520a 100644 --- a/pod/live/templates/live/live.html +++ b/pod/live/templates/live/direct.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% load i18n %} +{% load i18n event_tags%} {% load staticfiles %} {% block page_extra_head %} @@ -19,7 +19,7 @@ {% block breadcrumbs %} {{ block.super }} - + {% endblock %} @@ -28,10 +28,19 @@ {% block page_content %} {% if form %} - {% include 'live/live-form.html' %} + {% include 'live/event-form.html' %} {% else %}

 {{broadcaster.name}}

+ + {% if broadcaster.video_on_hold.is_video %}
@@ -48,16 +57,6 @@

- {% if broadcaster.aside_iframe_url %} -
- - -
- {% endif %} @@ -71,30 +70,12 @@

- {% if broadcaster.aside_iframe_url %} -
- - -
- {% endif %}
    {% endif %}

    {{ broadcaster.description|safe }}

    - {% if broadcaster.iframe_url != "" and broadcaster.iframe_url != None %} - - {% endif %} - {% if USE_BBB and USE_BBB_LIVE and display_chat %}
    @@ -124,6 +105,30 @@

    {% trans 'Send message' %}

    + {% if broadcaster.is_recording%} +
    +
    + {% blocktrans %}Recording in progress{% endblocktrans %} +
    +
    + {% else %} +
    +
    + {% blocktrans %}No recording in progress{% endblocktrans %} +
    +
    + {% endif %} + + {% get_next_events broadcaster.id 8 as NEXT_EVENTS %} + + {% if NEXT_EVENTS %} +
    +

    8 {% trans "Next events" %}

    + + {% include "live/events_list.html" with events=NEXT_EVENTS %} + +
    + {% endif %} {% endblock page_content %} @@ -156,7 +161,7 @@

    {% for otherbroadcaster in broadcaster.building.broadcaster_set.all%} {% if otherbroadcaster != broadcaster and otherbroadcaster.public %}

    - {%if otherbroadcaster.status %}{%if otherbroadcaster.password%}{%else%}{%endif%} {{otherbroadcaster.name}} + {%if otherbroadcaster.status %} {{otherbroadcaster.name}} {%else%} {{otherbroadcaster.name}} ({% trans "no broadcast in progress" %}){%endif%}

    {% endif %} diff --git a/pod/live/templates/live/building.html b/pod/live/templates/live/directs.html similarity index 77% rename from pod/live/templates/live/building.html rename to pod/live/templates/live/directs.html index ebe740ca0b..14f309bd38 100644 --- a/pod/live/templates/live/building.html +++ b/pod/live/templates/live/directs.html @@ -8,7 +8,7 @@ {% block breadcrumbs %} {{ block.super }} - + {% endblock %} @@ -28,9 +28,9 @@

     {{building.name}}

    + src="{% url 'live:direct' slug=broadcaster.slug %}?is_iframe=true">

    -
    {{broadcaster.name}}
    +
    {{broadcaster.name}}
    diff --git a/pod/live/templates/live/lives.html b/pod/live/templates/live/directs_all.html similarity index 82% rename from pod/live/templates/live/lives.html rename to pod/live/templates/live/directs_all.html index c5e7bb15f0..b8b42301c3 100644 --- a/pod/live/templates/live/lives.html +++ b/pod/live/templates/live/directs_all.html @@ -30,12 +30,12 @@
    {{building.name}} - {% if is_supervisor %}{% endif %} +
    {% for broadcaster in building.broadcaster_set.all %}

    - {%if broadcaster.status %}{%if broadcaster.password%}{%else%}{%endif%} {{broadcaster.name}} + {%if broadcaster.status %} {{broadcaster.name}} {%else%} {{broadcaster.name}} ({% trans "no broadcast in progress" %}){%endif%}

    {% empty %}

    {% trans "Sorry, no lives found" %}.

    diff --git a/pod/live/templates/live/event-all-info.html b/pod/live/templates/live/event-all-info.html new file mode 100644 index 0000000000..431b9c0b8e --- /dev/null +++ b/pod/live/templates/live/event-all-info.html @@ -0,0 +1,41 @@ +{% load i18n %} +{% load tagging_tags %} +

    + {{event.title|capfirst}} + {% if event.start_date %}[{{ event.start_date }}]{% endif %} + + + +

    + +
    + + {% include 'live/event-info.html' %} +
    \ No newline at end of file diff --git a/pod/live/templates/live/live-form.html b/pod/live/templates/live/event-form.html similarity index 86% rename from pod/live/templates/live/live-form.html rename to pod/live/templates/live/event-form.html index c43a62516f..3946c16dcd 100644 --- a/pod/live/templates/live/live-form.html +++ b/pod/live/templates/live/event-form.html @@ -1,7 +1,7 @@ {% load i18n %} -

    {% trans 'This live is protected by password, please fill in and click send.' %}

    - +

    {% trans 'This event is protected by password, please fill in and click send.' %}

    + {% csrf_token %}
    diff --git a/pod/live/templates/live/event-iframe.html b/pod/live/templates/live/event-iframe.html new file mode 100644 index 0000000000..235ebf1446 --- /dev/null +++ b/pod/live/templates/live/event-iframe.html @@ -0,0 +1,208 @@ +{% load i18n %} +{% load staticfiles %} +{% load tagging_tags %} +{% load thumbnail %} + + + + + + + {{ TITLE_SITE }} - {% block page_title %}{{ video.title }}{% endblock page_title %} + + + + + {% block page_extra_head %} + {% include 'videos/video-header.html' %} + {% endblock page_extra_head %} + + + + + + + + + + +{% block page_content %} +
    + + {% if event.is_current %} + +
    +
    +
    + +
    +
    +
    +
    +
    +

    {{ video.title }}

    + +
    +
    + {% include 'live/event-info.html' %} +
    +
    + {% elif event.is_past %} +
    + {% trans "Event is finished at : " %} {{ event.start_date }} {{ event.end_time }} +
    +
    + {% else %} +
    + {% trans "The event is scheduled on the : " %} {{ event.start_date }} + {% trans 'from' %} {{ event.start_time }} {% trans 'to' %} {{ event.end_time }} +
    + {% endif %} +
    + + {% block more_script %} + + {% if event.is_current %} + {% include 'videos/video-script.html' %} + + {% endif %} + + {% if not event.is_past %} + + {% endif %} + + {% endblock more_script %} + + + + {% if TRACKING_TEMPLATE %}{% include TRACKING_TEMPLATE %}{% endif %} +{% endblock page_content %} + + \ No newline at end of file diff --git a/pod/live/templates/live/event-info.html b/pod/live/templates/live/event-info.html new file mode 100644 index 0000000000..bb9eb093ce --- /dev/null +++ b/pod/live/templates/live/event-info.html @@ -0,0 +1,111 @@ +{% load i18n %} +{% load staticfiles %} +{% load tagging_tags %} +{% load thumbnail %} + +
    + {% if event.description %} +
    +

     {% trans 'Summary' %}

    +
    +

    {{ event.description|safe }}

    +
    + {% endif %} + +
    +

     {% trans 'Infos' %}

    +
    + +
    + + {% if event.is_draft == False or event.owner == request.user or request.user in event.additional_owners.all or request.user.is_superuser %} +
    +

     {% if event.is_draft %}{% trans 'Embed/Share (Draft Mode)' %}{% else %}{% trans 'Embed/Share' %}{%endif%}

    +
    + {% if event.is_draft %} +
    +
    + {% blocktrans %}Please note that your event is in draft mode.
    The following links contain a key allowing access. Anyone with this links can access it.{% endblocktrans %} +
    +
    + {% endif %} + +
    +
    +
    +  {% trans 'Embed in a web page' %} +
    + + +
    +
    + {% if not event.is_draft and not event.broadcaster.is_restricted %} +
    +
    {% trans 'Social Networks' %}
    +
    + +
    +
    + {% endif %} +
    +
    +
    +  {% trans 'Share the link' %} +
    + + +
    +
    + + qrcode +
    +
    +
    +
    +
    + {%endif%} +
    \ No newline at end of file diff --git a/pod/live/templates/live/event.html b/pod/live/templates/live/event.html new file mode 100644 index 0000000000..e484eb4905 --- /dev/null +++ b/pod/live/templates/live/event.html @@ -0,0 +1,539 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load staticfiles %} + +{% block page_extra_head %} + + + {% with 'player/videojs/lang/'|add:request.LANGUAGE_CODE|add:'.js' as videojs_lang %} + + {% endwith %} + + + {% if event.broadcaster.enable_viewer_count %} + + + {% endif %} +{% endblock %} + +{% if not request.GET.is_iframe %} + {% block breadcrumbs %}{{ block.super }} + + + + + {% endblock %} + + {% block page_title %}{{ event.title|title|truncatechars:45 }}{% endblock %} +{% endif %} + +{% block page_content %} + {% csrf_token %} +

     {{ event.title|title }}

    + + {% if form %} + {% include 'live/event-form.html' %} + {% else %} + {% if event.is_current %} + +
    +
    +
    + + +
    + {% if event.aside_iframe_url %} +
    + +
    + {% endif %} +
    + +
    + + {% if event.iframe_url != "" and event.iframe_url != None %} + + {% endif %} + + {% if not request.GET.is_iframe %} +
    + {% if need_piloting_buttons %} +
    + + + +
    + {% endif %} + + +
      + + {% endif %} + {% else %} + + + {% if event.is_past %} +
      + {% trans "Event is finished at : " %} {{ event.start_date }} {{ event.end_time }} +
      +
      + {% else %} +
      + {% trans "The event is scheduled on the : " %} {{ event.start_date }} + {% trans 'from' %} {{ event.start_time }} {% trans 'to' %} {{ event.end_time }} +
      + {% endif %} + {% endif %} + + {% if not request.GET.is_iframe %} +
      +
      +

      {% trans "Current event videos" %}

      +
      +
      +
      + +
      {% include 'live/event-all-info.html' %}
      + + {% endif %} + {% endif %} + +{% endblock %} + +{% if not request.GET.is_iframe %} + {% block page_aside %} + + {% include 'aside.html' with HIDE_DISCIPLINES=True HIDE_TAGS=True %} + + {% if event.owner == request.user or request.user in event.additional_owners.all or request.user.is_superuser or perms.live.delete_event or perms.live.change_event %} +
      +
       {% trans "Manage event" %} +
      +
      + + + + + + + + +
      +
      + {% endif %} + + {% endblock page_aside %} +{% endif %} + +{% block more_script %} + + + +{% endblock more_script %} \ No newline at end of file diff --git a/pod/live/templates/live/event_card.html b/pod/live/templates/live/event_card.html new file mode 100644 index 0000000000..c1e174e610 --- /dev/null +++ b/pod/live/templates/live/event_card.html @@ -0,0 +1,67 @@ +{% load l10n %} +{% load i18n %} +{% spaceless %} +
      + +
      +
      +{# between{% blocktrans with start_date=event.start_date|date:"d/m/Y" start_time=event.start_time end_time=event.end_time %}The {{start_date}} from {{start_time}} to {{end_time}}{% endblocktrans %}#} + + + {% if event.is_current %} + + {% else %} + + {% endif %} + + + + + {% if event.password %} + + + + {% endif %} + {% if event.is_draft %} + + + + {% endif %} + +
      +
      + +
      + {% if event.owner == request.user or request.user.is_superuser or perms.event.change_video or request.user in event.additional_owners.all %} + +
      + {% spaceless %} + + + + + + + {% endspaceless %} +
      + {% endif %} + + + {{event.title|capfirst|truncatechars:43}} + +
      + {% blocktrans with start_date=event.start_date|date:"d/m/Y" start_time=event.start_time end_time=event.end_time %}The {{start_date}} from {{start_time}} to {{end_time}}{% endblocktrans %} + {% if display_broadcaster_name %} + {{event.broadcaster.name|capfirst|truncatechars:20}} + {% endif %} +
      +
      + +
      +{% endspaceless %} \ No newline at end of file diff --git a/pod/live/templates/live/event_delete.html b/pod/live/templates/live/event_delete.html new file mode 100644 index 0000000000..400ab3b123 --- /dev/null +++ b/pod/live/templates/live/event_delete.html @@ -0,0 +1,78 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load staticfiles %} + +{% block breadcrumbs %} + {{ block.super }} + + + +{% endblock %} + +{% block page_title %} + {% blocktrans with vtitle=event.title %}Deleting the event "{{ vtitle }}"{% endblocktrans %} +{% endblock %} + +{% block page_content %} +

      {% blocktrans with vtitle=event.title %}Deleting the event "{{ vtitle }}"{% endblocktrans %} + + +  {% trans "Back to the event" %} + + +

      + + {% if event.is_current %} + + {% else %} + +

      {% trans 'To delete the event, please checked in and click send.' %}

      + + {% csrf_token %} +
      +
      + {% trans 'Agree required' %} + {% if form.errors %} +

      {% trans "One or more errors have been found in the form." %}

      + {% endif %} + {% for field_hidden in form.hidden_fields %} + {{ field_hidden }} + {% endfor %} + {% for field in form.visible_fields %} + {% spaceless %} +
      +
      + {{ field.errors }} + {% if "form-check-input" in field.field.widget.attrs.class %} +
      + {{ field }} +
      + {% else %} + + {% if "form-control-file" in field.field.widget.attrs.class and form.instance.event %} +
      {% endif %} + {{ field }} + {% endif %} + {% if field.help_text %} + {{ field.help_text|safe }} + {% endif %} + {% if field.field.required %} +
      {% trans "Please provide a valid value for this field" %}.
      {% endif %} +
      +
      + {% endspaceless %} + {% endfor %} +
      + + {% trans "Back to the event" %} +
      +
      +
      + + {% endif %} +{% endblock page_content %} \ No newline at end of file diff --git a/pod/live/templates/live/event_edit.html b/pod/live/templates/live/event_edit.html new file mode 100644 index 0000000000..587fdf9a8e --- /dev/null +++ b/pod/live/templates/live/event_edit.html @@ -0,0 +1,218 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load staticfiles %} + +{% block more_style %} + +{% endblock %} +{% block breadcrumbs %}{{ block.super }} + + {% if form.instance.title and form.instance.slug != '' %} + + + {% else %} + + {% endif %} +{% endblock %} + +{% block page_title %}{% if form.instance.title %}{% trans "Editing the event" %} "{{ form.instance.title }}"{% else %} + {% trans "Plan an event" %}{% endif %}{% endblock %} + +{% block collapse_page_aside %} + {% if access_not_allowed == True %} + + {% else %} + {{block.super}} + {% endif %} +{% endblock collapse_page_aside %} + +{% block page_content %} +

      + {% if form.instance.slug and form.instance.slug != '' %} + {% trans "Editing the event" %} + {% else %} + {% trans "Plan an event" %} + {% endif %} +

      + + {% if access_not_allowed == True %} +

      +  {% trans "Access to adding event has been restricted. If you want to add events on the platform, please" %} + {% trans 'contact us' %} +

      + {% else %} + {% if form.instance.slug and form.instance.slug != '' %} +
      + {% else %} + + {% endif %} + {% csrf_token %} + {% if form.instance.slug and form.instance.slug != '' and form.instance.is_current %} + + {% endif %} + + {% for field_hidden in form.hidden_fields %} + {{field_hidden}} + {% endfor %} + {% for field in form.visible_fields %} + {% spaceless %} +
      +
      + {{ field.errors }} + {% if "form-check-input" in field.field.widget.attrs.class %} +
      + {{ field }} +
      + {% else %} + + {{ field }} + {% endif %} + {% if field.help_text %} + {{ field.help_text|safe }} + {% endif %} + {% if field.field.required %} +
      {% trans "Please provide a valid value for this field" %}.
      + {% endif %} +
      +
      + {% endspaceless %} + {% endfor %} + +
      + +
      + +
      + {% endif %} +{% endblock %} + +{% block page_aside %} + {% if access_not_allowed == True %} + {% else %} + {% if form.instance.title and form.instance.slug != '' %} +
      +
       {% trans "Manage event" %} +
      +
      + {% if event.owner == request.user or request.user.is_superuser %} + + + + {% endif %} + + + +
      +
      + {% endif %} +
      +
      {% trans "Event planning" %}
      +
      +

      {% trans "You can schedule a live event by selecting a building and a room or recording device." %}

      +

      {% trans "You will then need to specify a date, a start time and an end time." %}

      +

      {% trans "Please note that 2 events cannot be scheduled in the same room simultaneously." %}

      +

      {% trans "Finally, remember to provide as precise a description as possible." %}

      +

      +
      +
      +
      {% trans "Mandatory fields" %}
      +
      +

      {% trans "Fields marked with an asterisk are mandatory." %}

      +
      +
      + {% endif %} +{% endblock page_aside %} + + +{% block more_script %} + + + + + + + {{ form.media }} + + + +{% endblock more_script %} \ No newline at end of file diff --git a/pod/live/templates/live/event_videos.html b/pod/live/templates/live/event_videos.html new file mode 100644 index 0000000000..2ad06cd85b --- /dev/null +++ b/pod/live/templates/live/event_videos.html @@ -0,0 +1,8 @@ +
      + {% for video in event.videos.all %} +
      + {% include "videos/card.html" %} +
      + {% endfor %} +
      \ No newline at end of file diff --git a/pod/live/templates/live/events.html b/pod/live/templates/live/events.html new file mode 100644 index 0000000000..35eb144ec1 --- /dev/null +++ b/pod/live/templates/live/events.html @@ -0,0 +1,57 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load staticfiles %} + +{% block more_style %} + +{% endblock more_style %} + +{% block breadcrumbs %}{{ block.super }} {% endblock %} + +{% block page_title %} + {% trans "Events" %} +{% endblock %} + +{% block page_content %} + + {% if use_category %} + +
      +
      +
      +
      +
      + + {% endif %} + +
      +

      {% blocktrans count counter=events.paginator.count %}{{ counter }} event found{% plural %}{{ counter }} events found{% endblocktrans %}

      + {% if display_direct_button %} + + + + {% endif %} + {% if display_creation_button %} + + + + {% endif %} +
      + + {% if events.paginator.count > 0 %} +

      {% trans "Please use the thumbnails toolbar which is located under the event on which you want to work with." %}

      + {% endif %} + + {% include "live/events_list.html" %} + +{% endblock page_content %} + +{% block page_aside %} + {% include 'live/filter_aside.html' %} +{% endblock page_aside %} \ No newline at end of file diff --git a/pod/live/templates/live/events_list.html b/pod/live/templates/live/events_list.html new file mode 100644 index 0000000000..b1fd4dc58b --- /dev/null +++ b/pod/live/templates/live/events_list.html @@ -0,0 +1,44 @@ +{% load i18n %} + +{% spaceless %} +
      + {% for event in events %} +
      + + {% include "live/event_card.html" %} +
      + {% empty %} +
      +

      {% trans "Sorry, no event found." %}

      +
      + {% endfor %} +
      + +
      + {% if events.has_previous %} + + {% trans "Previous page" %} + + {% endif %} + {% if events.has_next %} + + {% trans "Next page" %} + + {% endif %} +
      + + +{% endspaceless %} \ No newline at end of file diff --git a/pod/live/templates/live/events_next.html b/pod/live/templates/live/events_next.html new file mode 100644 index 0000000000..7eb138a909 --- /dev/null +++ b/pod/live/templates/live/events_next.html @@ -0,0 +1,18 @@ +{% load i18n event_tags %} + +{% get_next_events as NEXT_EVENTS %} + +{% if NEXT_EVENTS %} +
      +

      {% trans "Next events" %}

      + + {% include "live/events_list.html" with events=NEXT_EVENTS %} + + +
      +{% endif %} \ No newline at end of file diff --git a/pod/live/templates/live/filter_aside.html b/pod/live/templates/live/filter_aside.html new file mode 100644 index 0000000000..3d589c03c8 --- /dev/null +++ b/pod/live/templates/live/filter_aside.html @@ -0,0 +1,52 @@ +{% load i18n %} +{% load tagging_tags %} +{% load thumbnail %} + +{% spaceless %} +
      +

      +  {% trans "Filters" %}

      + +
      +
      + {% trans "Types" %} + + +
      +
      + {% for type in TYPES %} +
      + + +
      + {% endfor %} +
      + {% if TYPES.count > 5 %} + + + {% endif %} +
      +
      +
      + +
      + +{% endspaceless %} \ No newline at end of file diff --git a/pod/live/templates/live/my_events.html b/pod/live/templates/live/my_events.html new file mode 100644 index 0000000000..e3dba41d1e --- /dev/null +++ b/pod/live/templates/live/my_events.html @@ -0,0 +1,75 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load staticfiles %} + +{% block more_style %} + {% if use_category %} + + {% endif %} + +{% endblock more_style %} + +{% block breadcrumbs %}{{ block.super }} + {% endblock %} + +{% block page_title %} + {% blocktrans count counter=events_number %}{{ counter }} event found{% plural %}{{ counter }} events found{% endblocktrans %} +{% endblock %} + + + +{% block page_content %} + + {% if use_category %} + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + + + {# {% include "events/category_modal.html" %}#} + + {% endif %} + +
      + {% if events.count == 0 %} +

      {% trans "No event found" %}

      + {% else %} +

      {% blocktrans count counter=events_number %}{{ counter }} event found{% plural %}{{ counter }} events found{% endblocktrans %}

      + {% endif %} + {% if display_creation_button %} + + + + {% endif %} +
      + {% if events.count == 0 %} +

      {% trans 'You have not planned any event yet, please use the "Plan an event" button to add one' %}

      + {% else %} +

      {% trans "Please use the thumbnails toolbar which is located under the event on which you want to work with." %}

      + +

      {% trans "Coming events" %}

      + {% include "live/events_list.html" with events=coming_events urlpage=coming_events_url urland=past_events_url_page %} + +

      {% trans "Past events" %}

      + {% include "live/events_list.html" with events=past_events urlpage=past_events_url urland=coming_events_url_page %} + + {% endif %} + +{% endblock page_content %} + +{% block page_aside %} + {# masque la barre laterale aside.html #} +{% endblock page_aside %} \ No newline at end of file diff --git a/pod/live/templatetags/event_tags.py b/pod/live/templatetags/event_tags.py new file mode 100644 index 0000000000..22364fffd6 --- /dev/null +++ b/pod/live/templatetags/event_tags.py @@ -0,0 +1,30 @@ +from django import template +from datetime import date, datetime + +from pod.live.models import Event + +from django.db.models import Q + +register = template.Library() + + +@register.simple_tag(takes_context=True) +def get_next_events(context, broadcaster_id=None, limit_nb=4): + request = context["request"] + queryset = Event.objects.filter( + Q(start_date__gt=date.today()) + | (Q(start_date=date.today()) & Q(end_time__gte=datetime.now())) + ) + if broadcaster_id is None: + queryset = queryset.filter(is_draft=False) + else: + queryset = queryset.filter(broadcaster_id=broadcaster_id) + if not request.user.is_authenticated(): + queryset = queryset.filter(is_restricted=False) + # queryset = queryset.filter(broadcaster__restrict_access_to_groups__isnull=True) + # elif not request.user.is_superuser: + # queryset = queryset.filter(Q(is_draft=False) | Q(owner=request.user)) + # queryset = queryset.filter(Q(broadcaster__restrict_access_to_groups__isnull=True) | + # Q(broadcaster__restrict_access_to_groups__in=request.user.groups.all())) + + return queryset.all().order_by("start_date", "start_time")[:limit_nb] diff --git a/pod/live/tests/test_models.py b/pod/live/tests/test_models.py index 6988c33505..82bc50b1cb 100644 --- a/pod/live/tests/test_models.py +++ b/pod/live/tests/test_models.py @@ -1,10 +1,22 @@ +from datetime import date + +from django.core.exceptions import ValidationError from django.test import TestCase from django.conf import settings from django.contrib.auth.models import User from pod.video.models import Type from pod.video.models import Video -from ..models import Building, Broadcaster, HeartBeat +from ..models import ( + Building, + Broadcaster, + HeartBeat, + Event, + get_available_broadcasters_of_building, + get_building_having_available_broadcaster, + get_default_event_type, + present_or_future_date, +) from django.utils import timezone if getattr(settings, "USE_PODFILE", False): @@ -82,8 +94,6 @@ def setUp(self): status=True, is_restricted=True, building=building, - iframe_url="http://iframe.live", - iframe_height=120, public=False, ) # Test with a video on hold @@ -101,9 +111,6 @@ def setUp(self): is_restricted=False, video_on_hold=video_on_hold, building=building, - iframe_url="http://iframe2.live", - iframe_height=140, - password="mot2passe", ) print(" ---> SetUp of BroadcasterTestCase : OK !") @@ -116,19 +123,21 @@ def test_attributs(self): self.assertEqual(broadcaster.name, "broadcaster1") self.assertTrue("blabla" in broadcaster.poster.name) self.assertEqual(broadcaster.url, "http://test.live") - self.assertEqual(broadcaster.iframe_url, "http://iframe.live") - self.assertEqual(broadcaster.iframe_height, 120) self.assertEqual(broadcaster.status, True) self.assertEqual(broadcaster.public, False) self.assertEqual(broadcaster.is_restricted, True) self.assertEqual(broadcaster.building.id, 1) + self.assertEqual(broadcaster.sites.count(), 1) self.assertEqual( broadcaster.__str__(), "%s - %s" % (broadcaster.name, broadcaster.url), ) + self.assertEqual( + broadcaster.get_absolute_url(), "/live/direct/%s/" % broadcaster.slug + ) + broadcaster2 = Broadcaster.objects.get(id=2) self.assertEqual(broadcaster2.video_on_hold.id, 1) - self.assertEqual(broadcaster2.password, "mot2passe") print(" ---> test_attributs of BroadcasterTestCase : OK !") """ @@ -142,6 +151,12 @@ def test_delete_object(self): print(" ---> test_delete_object of BroadcasterTestCase : OK !") + def test_is_recording_admin(self): + html = Broadcaster.objects.get(id=1).is_recording_admin() + print(html) + expected_html = 'No' + self.assertEqual(html, expected_html) + class HeartbeatTestCase(TestCase): def setUp(self): @@ -152,8 +167,6 @@ def setUp(self): status=True, is_restricted=True, building=building, - iframe_url="http://iframe.live", - iframe_height=120, public=False, ) user = User.objects.create(username="pod") @@ -175,3 +188,164 @@ def test_attributs(self): self.assertEqual(hb.viewkey, "testkey") self.assertEqual(hb.broadcaster.name, "broadcaster1") print(" ---> test_attributs of HeartbeatTestCase : OK !") + + +def add_video(event): + e_video = Video.objects.get(id=1) + event.videos.add(e_video) + return event + + +class EventTestCase(TestCase): + def setUp(self): + building = Building.objects.create(name="building1") + building2 = Building.objects.create(name="building2") + e_broad = Broadcaster.objects.create( + name="broadcaster1", building=building, url="http://first.url", status=True + ) + Broadcaster.objects.create( + name="broadcaster2", building=building, url="http://second.url", status=True + ) + Broadcaster.objects.create( + name="broadcaster3", building=building, url="http://third.url", status=False + ) + Broadcaster.objects.create( + name="broad_b2", building=building2, url="http://firstb2.url", status=False + ) + e_user = User.objects.create(username="user1") + e_type = Type.objects.create(title="type1") + Video.objects.create( + video="event_video.mp4", + owner=e_user, + type=e_type, + ) + Event.objects.create( + title="event1", + owner=e_user, + broadcaster=e_broad, + type=e_type, + password="mdp", + iframe_url="http://iframe.live", + iframe_height=120, + aside_iframe_url="http://asideiframe.live", + ) + print("---> SetUp of EventTestCase : OK !") + + def test_class_methods(self): + self.assertEqual(get_default_event_type(), 1) + print(" ---> test_class_methods default_event_type : OK !") + + event = Event.objects.get(id=1) + defaut_event_start_date = event.start_date + self.assertEqual(present_or_future_date(defaut_event_start_date), date.today()) + + yesterday = defaut_event_start_date + timezone.timedelta(days=-1) + with self.assertRaises(ValidationError): + present_or_future_date(yesterday) + print(" ---> test_class_methods present_or_future_date : OK !") + + def test_create(self): + e_broad = Broadcaster.objects.get(id=1) + e_user = User.objects.get(id=1) + e_type = Type.objects.get(id=1) + event = Event.objects.create( + title="event2", + owner=e_user, + broadcaster=e_broad, + type=e_type, + ) + self.assertEqual(2, event.id) + print(" ---> test_create of EventTestCase : OK !") + + def test_attributs(self): + event = Event.objects.get(id=1) + self.assertEqual(event.title, "event1") + self.assertTrue(event.is_draft) + self.assertFalse(event.is_restricted) + self.assertFalse(event.is_auto_start) + self.assertEqual(event.description, "") + self.assertEqual(event.password, "mdp") + self.assertTrue(event.is_current()) + self.assertFalse(event.is_past()) + self.assertFalse(event.is_coming()) + self.assertEqual(event.videos.count(), 0) + self.assertEqual(event.restrict_access_to_groups.count(), 0) + self.assertEqual(event.iframe_url, "http://iframe.live") + self.assertEqual(event.iframe_height, 120) + self.assertEqual(event.aside_iframe_url, "http://asideiframe.live") + self.assertEqual( + event.__str__(), + "%s - %s" % ("%04d" % 1, "event1"), + ) + event.id = None + self.assertEqual(event.__str__(), "None") + self.assertEqual(event.get_thumbnail_url(), "/static/img/default-event.svg") + self.assertEqual(event.get_full_url(), "//example.com/live/event/0001-event1/") + print(" ---> test_attributs of EventTestCase : OK !") + + def test_add_thumbnail(self): + event = Event.objects.get(id=1) + if FILEPICKER: + fp_user, created = User.objects.get_or_create(username="pod") + homedir, created = UserFolder.objects.get_or_create( + name="Home", owner=fp_user + ) + thumb = CustomImageModel.objects.create( + folder=homedir, created_by=fp_user, file="blabla.jpg" + ) + else: + thumb = CustomImageModel.objects.create(file="blabla.jpg") + event.thumbnail = thumb + event.save() + self.assertTrue("blabla" in event.thumbnail.name) + print(" ---> test_add_thumbnail of EventTestCase : OK !") + + def test_add_video(self): + event = Event.objects.get(id=1) + event = add_video(event) + self.assertEquals(event.videos.count(), 1) + print(" ---> test_add_video of EventTestCase : OK !") + + def test_delete_object(self): + event = Event.objects.get(id=1) + event.delete() + self.assertEquals(Event.objects.all().count(), 0) + print(" ---> test_delete_object of EventTestCase : OK !") + + def test_delete_object_keep_video(self): + event = Event.objects.get(id=1) + add_video(event) + event.delete() + # video is not deleted with event + self.assertEquals(Video.objects.all().count(), 1) + print(" ---> test_delete_object_keep_video of EventTestCase : OK !") + + def test_event_filters(self): + user = User.objects.get(username="user1") + + # total Broadcaster + self.assertEqual(Broadcaster.objects.count(), 4) + print(" ---> test_filter broadcasters EventTestCase : OK !") + + # available broadcasters for user and building + filtered_broads = get_available_broadcasters_of_building(user, 1) + self.assertEqual(filtered_broads.count(), 2) + print(" ---> test_filtered broadcasters 1 of EventTestCase : OK !") + + # available broadcasters for user and building + the one passed in the param + filtered_broads = get_available_broadcasters_of_building(user, 1, 3) + self.assertEqual(filtered_broads.count(), 3) + print(" ---> test_filtered broadcasters 2 of EventTestCase : OK !") + + # total Building + self.assertEqual(Building.objects.count(), 2) + print(" ---> test_filter building EventTestCase : OK !") + + filtered_buildings = get_building_having_available_broadcaster(user) + self.assertEqual(filtered_buildings.count(), 1) + print(" ---> test_filtered buildings 2 of EventTestCase : OK !") + + # total plus + filtered_buildings = get_building_having_available_broadcaster(user, 2) + self.assertEqual(filtered_buildings.count(), 2) + print(" ---> test_filtered buildings 2 of EventTestCase : OK !") diff --git a/pod/live/tests/test_utils.py b/pod/live/tests/test_utils.py new file mode 100644 index 0000000000..06c396af48 --- /dev/null +++ b/pod/live/tests/test_utils.py @@ -0,0 +1,56 @@ +from django.test import TestCase +from django.contrib.auth.models import User +from pod.video.models import Type +from pod.live.models import Building, Broadcaster, Event + + +class LiveTestUtils(TestCase): + fixtures = [ + "initial_data.json", + ] + + def setUp(self): + building = Building.objects.create(name="building1") + e_broad = Broadcaster.objects.create( + name="broadcaster1", building=building, url="http://first.url", status=True + ) + e_user = User.objects.create(username="user1") + e_type = Type.objects.create(title="type1") + Event.objects.create( + title="event1", + owner=e_user, + broadcaster=e_broad, + type=e_type, + ) + + def test_utils(self): + from pod.live.utils import send_email_confirmation, get_bcc, get_cc + + event = Event.objects.get(id=1) + + bcc = get_bcc(1) + self.assertEquals(bcc, []) + print(" ---> test_utils get_bcc ok") + + expected = ["first", "second"] + bcc = get_bcc(expected) + self.assertEquals(bcc, expected) + print(" ---> test_utils get_bcc liste or tuple ok") + + expected = "first" + bcc = get_bcc(expected) + self.assertEquals(bcc, expected.split()) + print(" ---> test_utils get_bcc string ok") + + expected = ["emailadduser1", "emailadduser2"] + additional_user1 = User.objects.create(username="adduser1", email=expected[0]) + additional_user2 = User.objects.create(username="adduser2", email=expected[1]) + + event.additional_owners = [additional_user1, additional_user2] + cc = get_cc(event) + self.assertEquals(cc, expected) + print(" ---> test_utils get_cc ok") + + # TODO test this for real + send_email_confirmation(event) + print(" ---> test_utils send_email_confirmation ok") diff --git a/pod/live/tests/test_views.py b/pod/live/tests/test_views.py index de909214d9..bd1dfa8143 100644 --- a/pod/live/tests/test_views.py +++ b/pod/live/tests/test_views.py @@ -1,21 +1,24 @@ """ Unit tests for live views """ +import ast +import httmock +import pytz +from datetime import datetime from django.conf import settings -from django.test import TestCase -from django.test import Client from django.contrib.auth.models import User -from pod.live.models import Building, Broadcaster, HeartBeat -from pod.video.models import Video -from pod.video.models import Type from django.core.management import call_command +from django.http import JsonResponse, Http404 +from django.test import Client +from django.test import TestCase +from httmock import HTTMock, all_requests -# from django.core.exceptions import PermissionDenied -import ast -from django.http import JsonResponse -import datetime -import pytz - +from pod.authentication.models import AccessGroup +from pod.live.forms import EventForm, EventPasswordForm +from pod.live.models import Building, Broadcaster, HeartBeat, Event +from pod.video.models import Type +from pod.video.models import Video +from django.contrib.auth.models import Permission if getattr(settings, "USE_PODFILE", False): FILEPICKER = True @@ -63,18 +66,47 @@ def setUp(self): is_restricted=False, video_on_hold=video_on_hold, building=building, + piloting_implementation="wowza", + piloting_conf='{"server_url": "http://mock_api.fr", "application": "mock_name", "livestream": "mock_livestream"}', ) - + Event.objects.create( + title="event1", + owner=user, + is_restricted=False, + is_draft=False, + broadcaster=Broadcaster.objects.get(id=1), + type=Type.objects.get(id=1), + ) + AccessGroup.objects.create(code_name="group1", display_name="Group 1") print(" ---> SetUp of liveViewsTestCase : OK !") - def test_lives(self): + def test_directs(self): + # User not logged in self.client = Client() - response = self.client.get("/live/") - self.assertTemplateUsed(response, "live/lives.html") + self.user = User.objects.get(username="pod") + + response = self.client.get("/live/directs/") + self.assertRedirects( + response, + "%s?referrer=%s" % (settings.LOGIN_URL, "/live/directs/"), + status_code=302, + target_status_code=302, + ) + print(" ---> test_directs of liveViewsTestCase : OK !") + + # User logged without permission + self.client.force_login(self.user) + response = self.client.get("/live/directs/") + self.assertEqual(response.status_code, 403) + + # User with permission + permission = Permission.objects.get(codename="acces_live_pages") + self.user.user_permissions.add(permission) - print(" ---> test_lives of liveViewsTestCase : OK !") + response = self.client.get("/live/directs/") + self.assertTemplateUsed(response, "live/directs_all.html") - def test_building(self): + def test_directs_building(self): self.client = Client() self.user = User.objects.create( username="randomviewer", first_name="Jean", last_name="Viewer" @@ -86,12 +118,12 @@ def test_building(self): ) self.building = Building.objects.get(name="bulding1") - response = self.client.get("/live/building/%s/" % self.building.id) + response = self.client.get("/live/directs/%s/" % self.building.id) self.assertRedirects( response, "%s?referrer=%s" - % (settings.LOGIN_URL, "/live/building/%s/" % self.building.id), + % (settings.LOGIN_URL, "/live/directs/%s/" % self.building.id), status_code=302, target_status_code=302, ) @@ -99,17 +131,49 @@ def test_building(self): # User logged in self.client.force_login(self.user) # Broadcaster restricted - response = self.client.get("/live/building/%s/" % self.building.id) + response = self.client.get("/live/directs/%s/" % self.building.id) # self.assertRaises(PermissionDenied, response) self.assertEqual(response.status_code, 403) # User logged in self.client.force_login(self.superuser) # Broadcaster restricted - response = self.client.get("/live/building/%s/" % self.building.id) - self.assertTemplateUsed(response, "live/building.html") + response = self.client.get("/live/directs/%s/" % self.building.id) + self.assertTemplateUsed(response, "live/directs.html") + + print(" ---> test_directs_building of liveViewsTestCase : OK !") + + def test_direct(self): + self.client = Client() + self.user = User.objects.get(username="pod") + + # User not logged in + self.broadcaster = Broadcaster.objects.get(name="broadcaster1") + response = self.client.get("/live/direct/%s/" % self.broadcaster.slug) + self.assertRedirects( + response, + "%s?referrer=%s" + % (settings.LOGIN_URL, "/live/direct/%s/" % self.broadcaster.slug), + status_code=302, + target_status_code=302, + ) + + # User logged without permission + self.client.force_login(self.user) - print(" ---> test_building of liveViewsTestCase : OK !") + self.broadcaster = Broadcaster.objects.get(name="broadcaster1") + response = self.client.get("/live/direct/%s/" % self.broadcaster.slug) + self.assertEqual(response.status_code, 403) + + # User with permission + permission = Permission.objects.get(codename="acces_live_pages") + self.user.user_permissions.add(permission) + + self.broadcaster = Broadcaster.objects.get(name="broadcaster1") + response = self.client.get("/live/direct/%s/" % self.broadcaster.slug) + self.assertTemplateUsed(response, "live/direct.html") + + print(" ---> test_direct of liveViewsTestCase : OK !") def test_heartbeat(self): self.client = Client() @@ -121,7 +185,7 @@ def test_heartbeat(self): {}, False, False, - **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"} + **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}, ) self.assertEqual(response.status_code, 200) @@ -141,7 +205,7 @@ def test_heartbeat(self): {}, False, False, - **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"} + **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}, ) self.assertEqual(response.status_code, 200) @@ -155,23 +219,41 @@ def test_heartbeat(self): self.assertEqual(resp_content, exp_content) + # user with no permission self.client.force_login(self.user) response = self.client.get( "/live/ajax_calls/heartbeat/?key=testkeypod&liveid=1", {}, False, False, - **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"} + **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}, ) self.assertEqual(response.status_code, 200) - call_command("live_viewcounter") + data = {"viewers": 1, "viewers_list": []} + expected_content = JsonResponse(data, safe=False).content + exp_content = expected_content.decode("UTF-8") + exp_content = exp_content.replace("false", "False") + exp_content = ast.literal_eval(exp_content) + + resp_content = response.content.decode("UTF-8") + resp_content = resp_content.replace("false", "False") + resp_content = ast.literal_eval(resp_content) + + self.assertEqual(resp_content, exp_content) + + # superUser sees names + self.superuser = User.objects.create_superuser( + "superuser", "superuser@test.com", "psswd" + ) + call_command("live_viewcounter") + self.client.force_login(self.superuser) response = self.client.get( "/live/ajax_calls/heartbeat/?key=testkeypod&liveid=1", {}, False, False, - **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"} + **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}, ) self.assertEqual(response.status_code, 200) @@ -196,14 +278,15 @@ def test_heartbeat(self): self.assertEqual(resp_content, exp_content) + ## hb1 = HeartBeat.objects.get(viewkey="testkey") hb2 = HeartBeat.objects.get(viewkey="testkeypod") paris_tz = pytz.timezone("Europe/Paris") # make heartbeat expire now - hb1.last_heartbeat = paris_tz.localize(datetime.datetime(2012, 3, 3, 1, 30)) + hb1.last_heartbeat = paris_tz.localize(datetime(2012, 3, 3, 1, 30)) hb1.save() - hb2.last_heartbeat = paris_tz.localize(datetime.datetime(2012, 3, 3, 1, 30)) + hb2.last_heartbeat = paris_tz.localize(datetime(2012, 3, 3, 1, 30)) hb2.save() call_command("live_viewcounter") @@ -213,40 +296,739 @@ def test_heartbeat(self): print(" ---> test_heartbeat of liveViewsTestCase : OK !") - def test_video_live(self): + def test_events(self): self.client = Client() - self.user = User.objects.get(username="pod") - + access_group = AccessGroup.objects.get(code_name="group1") # User not logged in - # Broadcaster restricted - self.broadcaster = Broadcaster.objects.get(name="broadcaster1") - response = self.client.get("/live/%s/" % self.broadcaster.slug) + response = self.client.get("/") + self.assertTemplateUsed(response, "live/events_next.html") + print(" ---> test_events of / : OK !") + + response = self.client.get("/live/events/") + self.assertTemplateUsed(response, "live/events.html") + print(" ---> test_events of live/events : OK !") + + response = self.client.get("/live/events/?page=100") + self.assertTemplateUsed(response, "live/events.html") + print(" ---> test_events of live/events paginator empty: OK !") + + response = self.client.get("/live/events/?page=notint") + self.assertTemplateUsed(response, "live/events.html") + print(" ---> test_events of live/events paginator not integer: OK !") + + response = self.client.get("/live/my_events/") + self.assertRedirects( + response, + "%s?referrer=%s" % (settings.LOGIN_URL, "/live/my_events/"), + status_code=302, + target_status_code=302, + ) + print(" ---> test_events of live/my_events : OK !") + + # event is draft (permission denied) + self.event = Event.objects.get(title="event1") + + self.event.is_draft = True + self.event.save() + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertEqual(response.status_code, 403) + print(" ---> test_events access draft event : OK !") + + # with shared link + response = self.client.get( + "/live/event/%s/%s/" % (self.event.slug, self.event.get_hashkey()) + ) + self.assertTemplateUsed(response, "live/event.html") + print(" ---> test_events access draft with public link event : OK !") + + # event restricted and not draft (permission denied) + self.event.is_draft = False + self.event.is_restricted = True + self.event.save() + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertRedirects( + response, + "%s?referrer=%s" % (settings.LOGIN_URL, "/live/event/%s/" % self.event.slug), + status_code=302, + target_status_code=302, + ) + print(" ---> test_events access restricted and not draft event : OK !") + + # event restricted with access_groups (permission denied) + self.event.restrict_access_to_groups.add(access_group) + self.event.save() + response = self.client.get("/live/event/%s/" % self.event.slug) self.assertRedirects( response, - "%s?referrer=%s" % (settings.LOGIN_URL, "/live/%s/" % self.broadcaster.slug), + "%s?referrer=%s" % (settings.LOGIN_URL, "/live/event/%s/" % self.event.slug), status_code=302, target_status_code=302, ) - # Broadcaster not restricted - self.broadcaster = Broadcaster.objects.get(name="broadcaster2") - response = self.client.get("/live/%s/" % self.broadcaster.slug) - self.assertTemplateUsed(response, "live/live.html") + print(" ---> test_events restricted access group : OK !") + + # event restricted with password + self.event.is_restricted = False + self.event.restrict_access_to_groups = [] + event_pswd = "mypasswd" + self.event.password = event_pswd + self.event.save() + + url = f"/live/event/{self.event.slug}/" + response = self.client.get(url) + self.assertIsInstance(response.context["form"], EventPasswordForm) + messages = list(response.context["messages"]) + self.assertEqual(len(messages), 0) + + # event restricted with password (wrong) + response = self.client.post( + url, + {"password": "wrongpasswd"}, + ) + self.assertEqual(response.status_code, 200) + self.assertIsInstance(response.context["form"], EventPasswordForm) + messages = list(response.context["messages"]) + self.assertEqual(len(messages), 1) + print(" ---> test_events restricted with wrong password : OK !") + + # event restricted with password (provided) + response = self.client.post( + url, + {"password": event_pswd}, + ) + self.assertTrue("form" not in response.context) + print(" ---> test_events restricted with good password : OK !") + + # event not restricted nor draft + self.event.is_restricted = False + self.event.restrict_access_to_groups = [] + self.event.is_draft = False + self.event.password = None + self.event.save() + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertTemplateUsed(response, "live/event.html") + print(" ---> test_events access not restricted nor draft event : OK !") + + response = self.client.get( + "/live/event/%s/" % self.event.slug, {"is_iframe": True} + ) + self.assertTemplateUsed(response, "live/event-iframe.html") + print(" ---> test_events access not restricted nor draft iframe event : OK !") + + # event creation + response = self.client.get("/live/event_edit/") + self.assertRedirects( + response, + "%s?referrer=%s" % (settings.LOGIN_URL, "/live/event_edit/"), + status_code=302, + target_status_code=302, + ) + print(" ---> test_events creation event : OK !") # User logged in + self.user = User.objects.create(username="johndoe", password="johnpwd") self.client.force_login(self.user) - # Broadcaster restricted - self.broadcaster = Broadcaster.objects.get(name="broadcaster1") - response = self.client.get("/live/%s/" % self.broadcaster.slug) - self.assertTemplateUsed(response, "live/live.html") - # Broadcaster not restricted - self.broadcaster = Broadcaster.objects.get(name="broadcaster2") - response = self.client.get("/live/%s/" % self.broadcaster.slug) - self.assertTemplateUsed(response, "live/live.html") - - self.broadcaster.password = "password" - self.broadcaster.save() - response = self.client.get("/live/%s/" % self.broadcaster.slug) + + response = self.client.get("/live/my_events/") + self.assertTemplateUsed(response, "live/my_events.html") + print(" ---> test_events of live/my_events : OK !") + + response = self.client.get("/live/my_events/?ppage=100") + self.assertTemplateUsed(response, "live/my_events.html") + print(" ---> test_events of live/my_events paginator empty: OK !") + + response = self.client.get("/live/my_events/?ppage=notint") + self.assertTemplateUsed(response, "live/my_events.html") + print(" ---> test_events of live/my_events paginator not integer: OK !") + + # event draft (permission denied) + self.event = Event.objects.get(title="event1") + self.event.is_draft = True + self.event.save() + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertEqual(response.status_code, 403) + print(" ---> test_events access draft with logged user : OK !") + + # event restricted + self.event.is_draft = False + self.event.is_restricted = True + self.event.save() + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertTemplateUsed(response, "live/event.html") + print(" ---> test_events access restricted with logged user : OK !") + + # event restricted with access_groups (permission denied) + access_group = AccessGroup.objects.get(code_name="group1") + self.event.restrict_access_to_groups.add(access_group) + self.event.save() + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertEqual(response.status_code, 403) + print(" ---> test_events restricted access_group prevent : OK !") + + # event restricted with access_groups (user is in group) + self.user.owner.accessgroup_set.add(access_group) + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertEqual(200, response.status_code) + print(" ---> test_events restricted access group match: OK !") + + # piloting buttons (only for owner) + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertTemplateUsed(response, "live/event.html") + self.assertFalse(response.context["need_piloting_buttons"]) + print(" ---> test_events need_piloting_buttons event for not owner: OK !") + + # wrong event id + response = self.client.get("/live/event/what-ever/") + self.assertEqual(400, response.status_code) + print(" ---> test_events access nonexistent event : OK !") + + # event creation + response = self.client.get("/live/event_edit/") + self.assertTemplateUsed(response, "live/event_edit.html") + print(" ---> test_events creation event : OK !") + + # event edition (access_not_allowed) + response = self.client.get("/live/event_edit/%s/" % self.event.slug) + self.assertTemplateUsed(response, "live/event_edit.html") + self.assertEqual(response.context["access_not_allowed"], True) + print(" ---> test_events edit event access_not_allowed : OK !") + + # event delete (permission denied) + response = self.client.post("/live/event_delete/%s/" % self.event.slug) + self.assertEqual(response.status_code, 403) + print(" ---> test_events delete event : OK !") + + # User is event's owner + self.user = User.objects.get(username="pod") + self.client.force_login(self.user) + + self.event = Event.objects.get(title="event1") + self.event.is_draft = True + self.event.is_restricted = True + self.event.password = event_pswd + self.event.save() + + # myevents contains the event + response = self.client.get("/live/my_events/") + self.assertTemplateUsed(response, "live/my_events.html") + self.assertTemplateUsed(response, "live/events_list.html") + print(" ---> test_events owner sees his event's list: OK !") + + # user's event draft + self.event.is_draft = True + self.event.is_restricted = False + self.event.password = None + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertTemplateUsed(response, "live/event.html") + print(" ---> test_events access of restricted event for owner: OK !") + + # user's event restricted + self.event.is_draft = False + self.event.is_restricted = True + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertTemplateUsed(response, "live/event.html") + print(" ---> test_events access of restricted event for owner: OK !") + + # user's event password + self.event.password = event_pswd + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertTemplateUsed(response, "live/event.html") + print(" ---> test_events access of restricted event for owner: OK !") + + # user's event edition + response = self.client.get("/live/event_edit/%s/" % self.event.slug) + self.assertTemplateUsed(response, "live/event_edit.html") + self.assertIsInstance(response.context["form"], EventForm) + print(" ---> test_events edit event for owner : OK !") + + # piloting buttons + response = self.client.get("/live/event/%s/" % self.event.slug) + self.assertTemplateUsed(response, "live/event.html") + self.assertTrue(response.context["need_piloting_buttons"]) + print(" ---> test_events need_piloting_buttons event for owner: OK !") + + # Superuser logged in + self.superuser = User.objects.create_superuser( + "myuser", "myemail@test.com", "superpassword" + ) + self.client.force_login(self.superuser) + + # response = self.client.get("/") + # self.assertTemplateUsed(response, "live/events_next.html") + + # event edition + response = self.client.post("/live/event_edit/%s/" % self.event.slug) + self.assertTemplateUsed(response, "live/event_edit.html") + self.assertIsInstance(response.context["form"], EventForm) + print(" ---> test_events edit event for superuser : OK !") + + # event delete + response = self.client.post("/live/event_delete/%s/" % self.event.slug) + self.assertTemplateUsed(response, "live/event_delete.html") + print(" ---> test_events delete event for superuser : OK !") + + def test_get_broadcaster_by_slug(self): + from pod.live.views import get_broadcaster_by_slug + + broadcaster = Broadcaster.objects.get(id=1) + site = broadcaster.building.sites.first() + + broad = get_broadcaster_by_slug(1, site) + self.assertEqual(broad, broadcaster) + print(" ---> test get_broadcaster_by_slug : OK !") + + with self.assertRaises(Http404): + get_broadcaster_by_slug(1, None) + print(" ---> test get_broadcaster_by_slug No Site : OK !") + + with self.assertRaises(Http404): + get_broadcaster_by_slug(-1, site) + print(" ---> test get_broadcaster_by_slug Http404 : OK !") + + def test_broadcasters_from_building(self): + url = "/live/ajax_calls/getbroadcastersfrombuiding/" + response = self.client.get(url, {}) + self.assertEqual(response.status_code, 400) + print(" ---> test broadcasters_from_building HttpResponseBadRequest : OK !") + + response = self.client.get(url, {"building": "nonexistant"}) + self.assertEqual(response.status_code, 404) + print(" ---> test broadcasters_from_building HttpResponseBadRequest : OK !") + + response = self.client.get(url, {"building": "bulding1"}) + self.assertEqual(response.status_code, 200) + print(" ---> test broadcasters_from_building : OK !") + + # log as superUser to get all Broadcaster of building1 + self.superuser = User.objects.create_superuser( + "myuser", "myemail@test.com", "superpassword" + ) + self.client.force_login(self.superuser) + + response = self.client.get(url, {"building": "bulding1"}) + self.assertEqual(response.status_code, 200) + expected = { + "1": {"id": 1, "name": "broadcaster1", "restricted": True}, + "2": {"id": 2, "name": "broadcaster2", "restricted": False}, + } + self.assertEqual(response.json(), expected) + print(" ---> test broadcasters_from_building all : OK !") + + def test_broadcaster_restriction(self): + url = "/live/ajax_calls/getbroadcasterrestriction/" + response = self.client.post(url) + self.assertEqual(response.status_code, 405) + print(" ---> test broadcaster_restriction HttpResponseNotAllowed : OK !") + + response = self.client.get(url) + self.assertEqual(response.status_code, 400) + print(" ---> test broadcaster_restriction HttpResponseBadRequest : OK !") + + response = self.client.get(url, {"idbroadcaster": 1}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"restricted": True}) + print(" ---> test broadcaster_restriction : OK !") + + def test_isstreamavailabletorecord(self): + url = "/live/event_isstreamavailabletorecord/" + # not logged + response = self.client.get(url) + self.assertEqual(response.status_code, 302) + print(" ---> test isstreamavailabletorecord user not logged : OK !") + + # user logged + self.user = User.objects.get(username="pod") + self.client.force_login(self.user) + + response = self.client.post(url) + self.assertEqual(response.status_code, 405) + print(" ---> test isstreamavailabletorecord HttpResponseNotAllowed : OK !") + + response = self.client.get( + url, {"idbroadcaster": 1}, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + ) + self.assertEqual(response.status_code, 200) + # self.assertEqual(response.json(), {'available': False, 'recording': False, 'message': 'implementation error'}) + print(" ---> test isstreamavailabletorecord implementation error : OK !") + + def test_start_record(self): + url = "/live/ajax_calls/event_startrecord/" + + # not logged + response = self.client.post(url) + self.assertEqual(response.status_code, 302) + print(" ---> test startrecord user not logged : OK !") + + self.user = User.objects.get(username="pod") + self.client.force_login(self.user) + + response = self.client.get(url) + self.assertEqual(response.status_code, 405) + print(" ---> test startrecord HttpResponseNotAllowed : OK !") + + response = self.client.post( + url, + {"idbroadcaster": 1, "idevent": 1}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ) + self.assertEqual(response.status_code, 200) + # self.assertEqual(response.json(), {'success': False, 'message': 'implementation error'}) + print(" ---> test startrecord implementation error : OK !") + + def test_split_record(self): + url = "/live/ajax_calls/event_splitrecord/" + + # not logged + response = self.client.post(url) + self.assertEqual(response.status_code, 302) + print(" ---> test splitrecord user not logged : OK !") + + self.user = User.objects.get(username="pod") + self.client.force_login(self.user) + + response = self.client.get(url) + self.assertEqual(response.status_code, 405) + print(" ---> test splitrecord HttpResponseNotAllowed : OK !") + + response = self.client.post( + url, + {"idbroadcaster": 1, "idevent": 1}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ) + self.assertEqual(response.status_code, 200) + print(" ---> test splitrecord implementation error : OK !") + + def test_stop_record(self): + url = "/live/ajax_calls/event_stoprecord/" + + # not logged + response = self.client.post(url) + self.assertEqual(response.status_code, 302) + print(" ---> test stoprecord user not logged : OK !") + + self.user = User.objects.get(username="pod") + self.client.force_login(self.user) + + response = self.client.get(url) + self.assertEqual(response.status_code, 405) + print(" ---> test stoprecord HttpResponseNotAllowed : OK !") + + response = self.client.post( + url, + {"idbroadcaster": 1, "idevent": 1}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ) + self.assertEqual(response.status_code, 200) + print(" ---> test stoprecord implementation error : OK !") + + def test_event_info_record(self): + url = "/live/ajax_calls/geteventinforcurrentecord/" + + # not logged + response = self.client.post(url) + self.assertEqual(response.status_code, 302) + print(" ---> test event_info_record user not logged : OK !") + + self.user = User.objects.get(username="pod") + self.client.force_login(self.user) + + response = self.client.get(url) + self.assertEqual(response.status_code, 405) + print(" ---> test event_info_record HttpResponseNotAllowed : OK !") + + response = self.client.post( + url, + {"idbroadcaster": 1, "idevent": 1}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ) + self.assertEqual(response.status_code, 200) + print(" ---> test event_info_record implementation error : OK !") + + # Brodacaster with implementation parameters + @all_requests + def response_is_recording_ko(url, request): + return httmock.response( + 200, + { + "isConnected": False, + "isRecordingSet": False, + }, + ) + + @all_requests + def response_is_recording_ok(url, request): + return httmock.response( + 200, + { + "isConnected": True, + "isRecordingSet": True, + "segmentDuration": 3000, + }, + ) + + with HTTMock(response_is_recording_ko): + response = self.client.post( + url, + {"idbroadcaster": 2, "idevent": 1}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ) + self.assertEqual( + response.json(), + {"success": False, "error": "the broadcaster is not recording"}, + ) + print(" ---> test event_info_record not recording : OK !") + + with HTTMock(response_is_recording_ok): + response = self.client.post( + url, + {"idbroadcaster": 2, "idevent": 1}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ) + self.assertEqual(response.json(), {"success": True, "duration": 3}) + print(" ---> test event_info_record recording : OK !") + + def test_is_recording(self): + from pod.live.views import is_recording + + broadcaster = Broadcaster.objects.get(id=1) + broad_with_impl = Broadcaster.objects.get(id=2) + + # is_recording + @all_requests + def response_is_recording_ko(url, request): + return httmock.response( + 200, + { + "isConnected": False, + "isRecordingSet": False, + }, + ) + + @all_requests + def response_is_recording_ok(url, request): + return httmock.response( + 200, + { + "isConnected": True, + "isRecordingSet": True, + }, + ) + + response = is_recording(broadcaster) + self.assertFalse(response) + print(" ---> test misc_broadcaster is_recording no impl : OK !") + + with HTTMock(response_is_recording_ko): + response = is_recording(broad_with_impl, False) + self.assertFalse(response) + print(" ---> test misc_broadcaster is_recording no : OK !") + + with HTTMock(response_is_recording_ok): + response = is_recording(broad_with_impl, False) + self.assertTrue(response) + print(" ---> test misc_broadcaster is_recording yes : OK !") + + def test_is_available_to_record(self): + from pod.live.views import is_available_to_record + + broadcaster = Broadcaster.objects.get(id=1) + broad_with_impl = Broadcaster.objects.get(id=2) + + # is_available_to_record + @all_requests + def response_is_available_to_record_ok(url, request): + return httmock.response(200, {"isConnected": True, "isRecordingSet": False}) + + @all_requests + def response_is_recording_ok(url, request): + return httmock.response( + 200, + { + "isConnected": True, + "isRecordingSet": True, + }, + ) + + response = is_available_to_record(broadcaster) + self.assertFalse(response) + print(" ---> test misc_broadcaster is_available_to_record no impl: OK !") + + with HTTMock(response_is_recording_ok): + response = is_available_to_record(broad_with_impl) + self.assertFalse(response) + print(" ---> test misc_broadcaster is_available_to_record no : OK !") + + with HTTMock(response_is_available_to_record_ok): + response = is_available_to_record(broad_with_impl) + self.assertTrue(response) + print(" ---> test misc_broadcaster is_available_to_record yes : OK !") + + def test_method_start_record(self): + from pod.live.views import start_record + + broadcaster = Broadcaster.objects.get(id=1) + broad_with_impl = Broadcaster.objects.get(id=2) + + @all_requests + def response_created_ko(url, request): + return httmock.response(201, {"success": False}) + + @all_requests + def response_created_ok(url, request): + return httmock.response(201, {"success": True}) + + response = start_record(broadcaster, 1) + self.assertFalse(response) + print(" ---> test misc_broadcaster start_record no impl : OK !") + + with HTTMock(response_created_ko): + response = start_record(broad_with_impl, 1) + self.assertFalse(response) + print(" ---> test misc_broadcaster start_record no : OK !") + + with HTTMock(response_created_ok): + response = start_record(broad_with_impl, 1) + self.assertTrue(response) + print(" ---> test misc_broadcaster start_record yes : OK !") + + def test_method_split_record(self): + from pod.live.views import split_record + + broadcaster = Broadcaster.objects.get(id=1) + broad_with_impl = Broadcaster.objects.get(id=2) + + @all_requests + def response_ko(url, request): + return httmock.response(200, {"success": False}) + + @all_requests + def response_ok(url, request): + return httmock.response(200, {"success": True}) + + response = split_record(broadcaster) + self.assertFalse(response) + print(" ---> test misc_broadcaster split_record no impl : OK !") + + with HTTMock(response_ko): + response = split_record(broad_with_impl) + self.assertFalse(response) + print(" ---> test misc_broadcaster split_record no : OK !") + + with HTTMock(response_ok): + response = split_record(broad_with_impl) + self.assertTrue(response) + print(" ---> test misc_broadcaster split_record yes : OK !") + + def test_method_stop_record(self): + from pod.live.views import stop_record + + broadcaster = Broadcaster.objects.get(id=1) + broad_with_impl = Broadcaster.objects.get(id=2) + + @all_requests + def response_ko(url, request): + return httmock.response(200, {"success": False}) + + @all_requests + def response_ok(url, request): + return httmock.response(200, {"success": True}) + + response = stop_record(broadcaster) + self.assertFalse(response) + print(" ---> test misc_broadcaster stop_record no impl : OK !") + + with HTTMock(response_ko): + response = stop_record(broad_with_impl) + self.assertFalse(response) + print(" ---> test misc_broadcaster stop_record no : OK !") + + with HTTMock(response_ok): + response = stop_record(broad_with_impl) + self.assertTrue(response) + print(" ---> test misc_broadcaster stop_record yes : OK !") + + def test_method_info_current_record(self): + from pod.live.views import get_info_current_record + + broadcaster = Broadcaster.objects.get(id=1) + broad_with_impl = Broadcaster.objects.get(id=2) + + @all_requests + def response_info_current_record_ko(url, request): + return httmock.response(400, {"content": ""}) + + @all_requests + def response_info_current_record_ok(url, request): + return httmock.response( + 200, + { + "currentFile": "file_10.mp3", + "segmentNumber": "23", + "outputPath": "aa", + "segmentDuration": "60", + }, + ) + + expected_on_error = { + "currentFile": "", + "segmentNumber": "", + "outputPath": "", + "segmentDuration": "", + } + response = get_info_current_record(broadcaster) + self.assertEqual(response, expected_on_error) + print(" ---> test misc_broadcaster get_info_current_record no impl: OK !") + + with HTTMock(response_info_current_record_ko): + response = get_info_current_record(broad_with_impl) + self.assertEqual(response, expected_on_error) + print(" ---> test misc_broadcaster get_info_current_record error : OK !") + + with HTTMock(response_info_current_record_ok): + response = get_info_current_record(broad_with_impl) + self.assertEqual( + response, + { + "currentFile": "file_10.mp3", + "segmentNumber": "10", + "outputPath": "aa", + "segmentDuration": "60", + }, + ) + print(" ---> test misc_broadcaster get_info_current_record ok : OK !") + + def test_event_video_cards(self): + url = "/live/ajax_calls/geteventvideocards/" + + # not ajax + response = self.client.get(url) + self.assertEqual(response.status_code, 400) + print(" ---> test event_video_cards not ajax : OK !") + + response = self.client.get( + url, {"idevent": 1}, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + ) + self.assertEqual(response.json(), {"content": ""}) + print(" ---> test event_video_cards empty : OK !") + + video = Video.objects.get(id=1) + event = Event.objects.get(id=1) + event.videos.add(video) + event.save() + + response = self.client.get( + url, {"idevent": 1}, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + ) self.assertEqual(response.status_code, 200) - self.assertTrue(response.context["form"]) + self.assertNotEqual(response.json(), {"content": ""}) + print(" ---> test event_video_cards with videos : OK !") + + def test_event_dir_exists(self): + from pod.live.views import checkDirExists, checkFileExists + + with self.assertRaises(Exception): + checkDirExists("dirname", 2) + print(" ---> test checkDirExists exception : OK !") - print(" ---> test_video_live of liveViewsTestCase : OK !") + with self.assertRaises(Exception): + checkFileExists("filename", 2) + print(" ---> test checkFileExists exception : OK !") diff --git a/pod/live/urls.py b/pod/live/urls.py index ced5483dc6..3a55deb2df 100644 --- a/pod/live/urls.py +++ b/pod/live/urls.py @@ -1,12 +1,90 @@ from django.conf.urls import url -from .views import lives, heartbeat, building -from .views import video_live + +from .views import ( + broadcasters_from_building, + event, + events, + event_edit, + event_delete, + heartbeat, + my_events, + direct, + directs, + directs_all, + ajax_event_startrecord, + ajax_event_stoprecord, + ajax_event_splitrecord, + event_isstreamavailabletorecord, + event_video_transform, + event_get_video_cards, + ajax_event_info_record, + broadcaster_restriction, +) app_name = "live" -urlpatterns = [ +urlpatterns = [] + +urlpatterns += [ + # url(r"^$", lives, name="lives"), + url( + r"^ajax_calls/event_startrecord/$", + ajax_event_startrecord, + name="ajax_event_startrecord", + ), + url( + r"^ajax_calls/event_stoprecord/$", + ajax_event_stoprecord, + name="ajax_event_stoprecord", + ), + url( + r"^ajax_calls/event_splitrecord/$", + ajax_event_splitrecord, + name="ajax_event_splitrecord", + ), + url( + r"^ajax_calls/getbroadcastersfrombuiding/$", + broadcasters_from_building, + name="broadcasters_from_building", + ), + url( + r"^ajax_calls/getbroadcasterrestriction/$", + broadcaster_restriction, + name="ajax_broadcaster_restriction", + ), + url( + r"^ajax_calls/geteventinforcurrentecord/$", + ajax_event_info_record, + name="ajax_event_info_record", + ), + url( + r"^ajax_calls/geteventvideocards/$", + event_get_video_cards, + name="event_get_video_cards", + ), url(r"^ajax_calls/heartbeat/", heartbeat), - url(r"^$", lives, name="lives"), - url(r"^building/(?P[\d]+)/$", building, name="building"), - url(r"^(?P[\-\d\w]+)/$", video_live, name="video_live"), + url(r"^direct/(?P[\-\d\w]+)/$", direct, name="direct"), + url(r"^directs/$", directs_all, name="directs_all"), + url(r"^directs/(?P[\d]+)/$", directs, name="directs"), + url(r"^event/(?P[\-\d\w]+)/$", event, name="event"), + url( + r"^event/(?P[\-\d\w]+)/(?P[\-\d\w]+)/$", + event, + name="event_private", + ), + url(r"^event_edit/$", event_edit, name="event_edit"), + url(r"^event_edit/(?P[\-\d\w]+)/$", event_edit, name="event_edit"), + url(r"^event_delete/(?P[\-\d\w]+)/$", event_delete, name="event_delete"), + url( + r"^event_isstreamavailabletorecord/$", + event_isstreamavailabletorecord, + name="event_isstreamavailabletorecord", + ), + url( + r"^event_video_transform/$", + event_video_transform, + name="event_video_transform", + ), + url(r"^events/$", events, name="events"), + url(r"^my_events/$", my_events, name="my_events"), ] diff --git a/pod/live/utils.py b/pod/live/utils.py new file mode 100644 index 0000000000..a68272bf2c --- /dev/null +++ b/pod/live/utils.py @@ -0,0 +1,169 @@ +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ +from django.core.mail import EmailMultiAlternatives, mail_managers + +SECURE_SSL_REDIRECT = getattr(settings, "SECURE_SSL_REDIRECT", False) + +TEMPLATE_VISIBLE_SETTINGS = getattr( + settings, + "TEMPLATE_VISIBLE_SETTINGS", + { + "TITLE_SITE": "Pod", + "TITLE_ETB": "University name", + "LOGO_SITE": "img/logoPod.svg", + "LOGO_ETB": "img/logo_etb.svg", + "LOGO_PLAYER": "img/logoPod.svg", + "LINK_PLAYER": "", + "FOOTER_TEXT": ("",), + "FAVICON": "img/logoPod.svg", + "CSS_OVERRIDE": "", + "PRE_HEADER_TEMPLATE": "", + "POST_FOOTER_TEMPLATE": "", + "TRACKING_TEMPLATE": "", + }, +) + +TITLE_SITE = ( + TEMPLATE_VISIBLE_SETTINGS["TITLE_SITE"] + if (TEMPLATE_VISIBLE_SETTINGS.get("TITLE_SITE")) + else "Pod" +) + +DEFAULT_FROM_EMAIL = getattr(settings, "DEFAULT_FROM_EMAIL", "noreply@univ.fr") + +USE_ESTABLISHMENT = getattr(settings, "USE_ESTABLISHMENT_FIELD", False) + +MANAGERS = getattr(settings, "MANAGERS", {}) + +DEBUG = getattr(settings, "DEBUG", True) + + +def send_email_confirmation(event): + """Send an email on creation/modification event.""" + if DEBUG: + print("SEND EMAIL ON EVENT SCHEDULING") + + url_event = get_event_url(event) + + subject = "[%s] %s" % ( + TITLE_SITE, + _("Registration of event #%(content_id)s") % {"content_id": event.id}, + ) + + from_email = DEFAULT_FROM_EMAIL + + to_email = [event.owner.email] + + message = "%s\n%s\n\n%s\n" % ( + _("Hello,"), + _( + "You have just scheduled a new event called “%(content_title)s” in date of %(start_date)s from %(start_time)s to %(end_time)s on video server : %(url_event)s)" + + ". You can find the other sharing options in the dedicated tab." + ) + % { + "content_title": event.title, + "start_date": event.start_date.strftime("%d/%m/%Y"), + "start_time": event.start_time, + "end_time": event.end_time, + "url_event": url_event, + }, + _("Regards."), + ) + + full_message = message + "\n%s%s" % ( + _("Post by:"), + event.owner, + ) + + html_message = "

      %s

      %s

      %s

      " % ( + _("Hello,"), + _( + "You have just scheduled a new event called “%(content_title)s” in date of %(start_date)s from %(start_time)s to %(end_time)s on video server : %(url_event)s)" + + ". You can find the other sharing options in the dedicated tab." + ) + % { + "content_title": event.title, + "start_date": event.start_date.strftime("%d/%m/%Y"), + "start_time": event.start_time, + "end_time": event.end_time, + "url_event": url_event, + }, + _("Regards."), + ) + + # email establishment + if ( + USE_ESTABLISHMENT + and MANAGERS + and event.owner.owner.establishment.lower() in dict(MANAGERS) + ): + send_establishment(event, subject, message, from_email, to_email, html_message) + return + + # email to managers + send_managers(event.owner, subject, full_message, False, html_message) + + # send email + cc_email = get_cc(event) + send_email(subject, message, from_email, to_email, cc_email, html_message) + + +def get_event_url(event): + url_scheme = "https" if SECURE_SSL_REDIRECT else "http" + url_event = "%s:%s" % (url_scheme, event.get_full_url()) + + if event.is_draft: + url_event += event.get_hashkey() + "/" + return url_event + + +def get_bcc(manager): + if type(manager) in (list, tuple): + return manager + elif type(manager) == str: + return [manager] + return [] + + +def get_cc(event): + to_cc = [] + for additional_owners in event.additional_owners.all(): + to_cc.append(additional_owners.email) + return to_cc + + +def send_establishment(event, subject, message, from_email, to_email, html_message): + event_estab = event.owner.owner.establishment.lower() + manager = dict(MANAGERS)[event_estab] + bcc_email = get_bcc(manager) + msg = EmailMultiAlternatives(subject, message, from_email, to_email, bcc=bcc_email) + msg.attach_alternative(html_message, "text/html") + msg.send() + + +def send_managers(owner, subject, full_message, fail, html_message): + full_html_message = html_message + "
      %s%s" % ( + _("Post by:"), + owner, + ) + mail_managers( + subject, + full_message, + fail_silently=fail, + html_message=full_html_message, + ) + + +def send_email(subject, message, from_email, to_email, cc_email, html_message): + msg = EmailMultiAlternatives( + subject, + message, + from_email, + to_email, + cc=cc_email, + ) + + msg.attach_alternative(html_message, "text/html") + + if not DEBUG: + msg.send() diff --git a/pod/live/views.py b/pod/live/views.py index 93b645bd4d..1f487fb5c7 100644 --- a/pod/live/views.py +++ b/pod/live/views.py @@ -1,30 +1,74 @@ -from django.shortcuts import render -from django.shortcuts import get_object_or_404 -from .models import Building, Broadcaster, HeartBeat -from .forms import LivePasswordForm +import json +import logging +import os.path +import re +from datetime import date, datetime, timedelta +from time import sleep + from django.conf import settings -from django.shortcuts import redirect -from django.contrib.sites.shortcuts import get_current_site from django.contrib import messages -from django.utils.translation import ugettext_lazy as _ -from django.db.models import Prefetch -from django.core.exceptions import ObjectDoesNotExist -from django.http import HttpResponse, HttpResponseBadRequest from django.contrib.auth.decorators import login_required +from django.contrib.sites.shortcuts import get_current_site +from django.core.exceptions import ObjectDoesNotExist, SuspiciousOperation from django.core.exceptions import PermissionDenied -import json +from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage +from django.db.models import Prefetch +from django.db.models import Q +from django.http import ( + HttpResponse, + HttpResponseBadRequest, + JsonResponse, + HttpResponseNotAllowed, + HttpResponseNotFound, +) +from django.shortcuts import get_object_or_404 +from django.shortcuts import redirect +from django.shortcuts import render +from django.template.loader import render_to_string +from django.urls import reverse from django.utils import timezone -from pod.bbb.models import Livestream +from django.utils.translation import ugettext_lazy as _ +from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect -VIEWERS_ONLY_FOR_STAFF = getattr(settings, "VIEWERS_ONLY_FOR_STAFF", False) +from pod.bbb.models import Livestream +from . import pilotingInterface +from .forms import EventPasswordForm, EventForm, EventDeleteForm +from .models import ( + Building, + Broadcaster, + HeartBeat, + Event, + get_available_broadcasters_of_building, +) +from .utils import send_email_confirmation +from ..main.views import in_maintenance +from ..video.models import Video HEARTBEAT_DELAY = getattr(settings, "HEARTBEAT_DELAY", 45) USE_BBB = getattr(settings, "USE_BBB", False) USE_BBB_LIVE = getattr(settings, "USE_BBB_LIVE", False) +DEFAULT_EVENT_PATH = getattr(settings, "DEFAULT_EVENT_PATH", "") +DEFAULT_EVENT_THUMBNAIL = getattr( + settings, "DEFAULT_EVENT_THUMBNAIL", "/img/default-event.svg" +) +AFFILIATION_EVENT = getattr( + settings, "AFFILIATION_EVENT", ("faculty", "employee", "staff") +) +VIDEOS_DIR = getattr(settings, "VIDEOS_DIR", "videos") + +logger = logging.getLogger("pod.live") + +EMAIL_ON_EVENT_SCHEDULING = getattr(settings, "EMAIL_ON_EVENT_SCHEDULING", False) + + +@login_required(redirect_field_name="referrer") +def directs_all(request): # affichage des directs + if not (request.user.is_superuser or request.user.has_perm("live.acces_live_pages")): + messages.add_message(request, messages.ERROR, _("You cannot view this page.")) + raise PermissionDenied -def lives(request): # affichage des directs site = get_current_site(request) buildings = ( Building.objects.all() @@ -38,42 +82,28 @@ def lives(request): # affichage des directs ) return render( request, - "live/lives.html", + "live/directs_all.html", { "buildings": buildings, - "is_supervisor": ( - request.user.is_superuser - or request.user.has_perm("live.view_building_supervisor") - ), }, ) @login_required(redirect_field_name="referrer") -def building(request, building_id): # affichage des directs - if not ( - request.user.is_superuser - or request.user.has_perm("live.view_building_supervisor") - ): +def directs(request, building_id): # affichage des directs d'un batiment + if not (request.user.is_superuser or request.user.has_perm("live.acces_live_pages")): messages.add_message(request, messages.ERROR, _("You cannot view this page.")) raise PermissionDenied building = get_object_or_404(Building, id=building_id) - return render(request, "live/building.html", {"building": building}) - + return render(request, "live/directs.html", {"building": building}) -def get_broadcaster_by_slug(slug, site): - broadcaster = None - if slug.isnumeric(): - try: - broadcaster = Broadcaster.objects.get(id=slug, building__sites=site) - except ObjectDoesNotExist: - pass - if broadcaster is None: - broadcaster = get_object_or_404(Broadcaster, slug=slug, building__sites=site) - return broadcaster +@login_required(redirect_field_name="referrer") +def direct(request, slug): # affichage du flux d'un diffuseur + if not (request.user.is_superuser or request.user.has_perm("live.acces_live_pages")): + messages.add_message(request, messages.ERROR, _("You cannot view this page.")) + raise PermissionDenied -def video_live(request, slug): # affichage des directs site = get_current_site(request) broadcaster = get_broadcaster_by_slug(slug, site) if broadcaster.is_restricted and not request.user.is_authenticated(): @@ -82,28 +112,6 @@ def video_live(request, slug): # affichage des directs "%s?%sreferrer=%s" % (settings.LOGIN_URL, iframe_param, request.get_full_path()) ) - is_password_protected = ( - broadcaster.password is not None and broadcaster.password != "" - ) - if is_password_protected and not ( - request.POST.get("password") - and request.POST.get("password") == broadcaster.password - ): - form = LivePasswordForm(request.POST) if request.POST else LivePasswordForm() - if ( - request.POST.get("password") - and request.POST.get("password") != broadcaster.password - ): - messages.add_message(request, messages.ERROR, _("The password is incorrect.")) - return render( - request, - "live/live.html", - { - "broadcaster": broadcaster, - "form": form, - "heartbeat_delay": HEARTBEAT_DELAY, - }, - ) # Search if broadcaster is used to display a BBB streaming live # for which students can send message from this live page display_chat = False @@ -113,7 +121,7 @@ def video_live(request, slug): # affichage des directs display_chat = livestream.enable_chat return render( request, - "live/live.html", + "live/direct.html", { "display_chat": display_chat, "broadcaster": broadcaster, @@ -122,6 +130,18 @@ def video_live(request, slug): # affichage des directs ) +def get_broadcaster_by_slug(slug, site): + broadcaster = None + if type(slug) == int: + try: + broadcaster = Broadcaster.objects.get(id=slug, building__sites=site) + except ObjectDoesNotExist: + pass + if broadcaster is None: + broadcaster = get_object_or_404(Broadcaster, slug=slug, building__sites=site) + return broadcaster + + """ use rest api to change status def change_status(request, slug): broadcaster = get_object_or_404(Broadcaster, slug=slug) @@ -150,9 +170,20 @@ def heartbeat(request): mimetype = "application/json" viewers = broadcast.viewers.values("first_name", "last_name", "is_superuser") - can_see = ( - VIEWERS_ONLY_FOR_STAFF and request.user.is_staff - ) or not VIEWERS_ONLY_FOR_STAFF + + current_event = Event.objects.filter( + Q(broadcaster_id=broadcaster_id) + & Q(start_date=date.today()) + & (Q(start_date__lte=datetime.now()) & Q(end_time__gte=datetime.now())) + ).first() + if current_event is None: + can_see = request.user.is_superuser + else: + can_see = ( + request.user.is_superuser + or request.user == current_event.owner + or request.user in current_event.additional_owners.all() + ) return HttpResponse( json.dumps( { @@ -163,3 +194,728 @@ def heartbeat(request): mimetype, ) return HttpResponseBadRequest() + + +def can_manage_event(user): + if not user.is_authenticated: + return False + if user.is_superuser: + return True + return user.owner.accessgroup_set.filter( + code_name__in=settings.AFFILIATION_EVENT + ).exists() + + +def is_in_event_groups(user, event): + return user.owner.accessgroup_set.filter( + code_name__in=[ + name[0] for name in event.restrict_access_to_groups.values_list("code_name") + ] + ).exists() + + +def get_event_access(request, event, slug_private, is_owner): + """Return True if access is granted to current user.""" + + if is_owner: + return True + + if event.is_draft: + if slug_private or slug_private == event.get_hashkey(): + can_access_draft = True + else: + can_access_draft = ( + request.user == event.owner + or request.user in event.additional_owners.all() + or request.user.is_superuser + ) + if not can_access_draft: + return False + + if event.is_restricted and not request.user.is_authenticated(): + return False + + if event.restrict_access_to_groups.all().exists() and ( + not request.user.is_authenticated() or not is_in_event_groups(request.user, event) + ): + return False + + return True + + +def event(request, slug, slug_private=None): # affichage d'un event + + # modif de l'url d'appel pour compatibilité avec le template link_video.html (variable : urleditapp) + request.resolver_match.namespace = "" + + try: + id = int(slug[: slug.find("-")]) + except ValueError: + raise SuspiciousOperation("Invalid event id") + + evemnt = get_object_or_404(Event, id=id) + + if evemnt.is_restricted and not request.user.is_authenticated(): + url = reverse("authentication_login") + url += "?referrer=" + request.get_full_path() + return redirect(url) + + user_owns_event = request.user.is_authenticated() and ( + evemnt.owner == request.user + or request.user in evemnt.additional_owners.all() + or request.user.is_superuser + ) + + if not get_event_access(request, evemnt, slug_private, user_owns_event): + messages.add_message(request, messages.ERROR, _("You cannot watch this event.")) + raise PermissionDenied + + return render_event_template(request, evemnt, user_owns_event) + + +def render_event_template(request, evemnt, user_owns_event): + is_password_protected = evemnt.password is not None and evemnt.password != "" + password_provided = request.POST.get("password") is not None + password_correct = request.POST.get("password") == evemnt.password + + # password protection + if is_password_protected and not user_owns_event and not password_correct: + form = EventPasswordForm(request.POST) if request.POST else EventPasswordForm() + if password_provided: + messages.add_message(request, messages.ERROR, _("The password is incorrect.")) + return render( + request, + "live/event.html", + { + "event": evemnt, + "form": form, + "need_piloting_buttons": False, + "heartbeat_delay": HEARTBEAT_DELAY, + }, + ) + + template_event = "live/event.html" + if request.GET.get("is_iframe"): + template_event = "live/event-iframe.html" + + return render( + request, + template_event, + { + "event": evemnt, + "need_piloting_buttons": user_owns_event, + "heartbeat_delay": HEARTBEAT_DELAY, + }, + ) + + +def events(request): # affichage des events + + queryset = Event.objects.filter( + Q(start_date__gt=date.today()) + | (Q(start_date=date.today()) & Q(end_time__gte=datetime.now())) + ) + queryset = queryset.filter(is_draft=False) + if not request.user.is_authenticated(): + queryset = queryset.filter(is_restricted=False) + # queryset = queryset.filter(broadcaster__restrict_access_to_groups__isnull=True) + # elif not request.user.is_superuser: + # queryset = queryset.filter(Q(is_draft=False) | Q(owner=request.user)) + # queryset = queryset.filter(Q(broadcaster__restrict_access_to_groups__isnull=True) | + # Q(broadcaster__restrict_access_to_groups__in=request.user.groups.all())) + + events_list = queryset.all().order_by("start_date", "start_time", "end_time") + + 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(events_list, 12) + try: + events_found = paginator.page(page) + except PageNotAnInteger: + events_found = paginator.page(1) + except EmptyPage: + events_found = paginator.page(paginator.num_pages) + + return render( + request, + "live/events.html", + { + "events": events_found, + "full_path": full_path, + "DEFAULT_EVENT_THUMBNAIL": DEFAULT_EVENT_THUMBNAIL, + "display_broadcaster_name": False, + "display_direct_button": request.user.is_superuser + or request.user.has_perm("live.acces_live_pages"), + "display_creation_button": can_manage_event(request.user), + }, + ) + + +@csrf_protect +@ensure_csrf_cookie +@login_required(redirect_field_name="referrer") +def my_events(request): + queryset = request.user.event_set.all() | request.user.owners_events.all() + + past_events = ( + queryset.filter( + Q(start_date__lt=date.today()) + | (Q(start_date=date.today()) & Q(end_time__lte=datetime.now())) + ) + .all() + .order_by("-start_date", "-start_time", "-end_time") + ) + + coming_events = ( + queryset.filter( + Q(start_date__gt=date.today()) + | (Q(start_date=date.today()) & Q(end_time__gte=datetime.now())) + ) + .all() + .order_by("start_date", "start_time", "end_time") + ) + + events_number = queryset.all().distinct().count() + + PREVIOUS_EVENT_URL_NAME = "ppage" + NEXT_EVENT_URL_NAME = "npage" + + full_path = request.get_full_path() + full_path = re.sub(r"\?|&" + PREVIOUS_EVENT_URL_NAME + r"=\d+", "", full_path) + full_path = re.sub(r"\?|&" + NEXT_EVENT_URL_NAME + r"=\d+", "", full_path) + + paginatorComing = Paginator(coming_events, 8) + paginatorPast = Paginator(past_events, 8) + + pageP = request.GET.get(PREVIOUS_EVENT_URL_NAME, 1) + pageN = request.GET.get(NEXT_EVENT_URL_NAME, 1) + + try: + coming_events = paginatorComing.page(pageN) + past_events = paginatorPast.page(pageP) + except PageNotAnInteger: + pageP = 1 + pageN = 1 + coming_events = paginatorComing.page(1) + past_events = paginatorPast.page(1) + except EmptyPage: + pageP = 1 + pageN = 1 + coming_events = paginatorComing.page(paginatorComing.num_pages) + past_events = paginatorPast.page(paginatorPast.num_pages) + + return render( + request, + "live/my_events.html", + { + "full_path": full_path, + "types": request.GET.getlist("type"), + "events_number": events_number, + "past_events": past_events, + "past_events_url": PREVIOUS_EVENT_URL_NAME, + "past_events_url_page": PREVIOUS_EVENT_URL_NAME + "=" + str(pageP), + "coming_events": coming_events, + "coming_events_url": NEXT_EVENT_URL_NAME, + "coming_events_url_page": NEXT_EVENT_URL_NAME + "=" + str(pageN), + "DEFAULT_EVENT_THUMBNAIL": DEFAULT_EVENT_THUMBNAIL, + "display_broadcaster_name": True, + "display_creation_button": can_manage_event(request.user), + }, + ) + + +def get_event_edition_access(request, event): + # creation + if event is None: + return can_manage_event(request.user) + # edition + if ( + request.user == event.owner + or request.user in event.additional_owners.all() + or request.user.is_superuser + ): + return True + + return False + + +@csrf_protect +@ensure_csrf_cookie +@login_required(redirect_field_name="referrer") +def event_edit(request, slug=None): + + if in_maintenance(): + return redirect(reverse("maintenance")) + + event = get_object_or_404(Event, slug=slug) if slug else None + if not get_event_edition_access(request, event): + return render(request, "live/event_edit.html", {"access_not_allowed": True}) + + form = EventForm( + request.POST or None, + instance=event, + user=request.user, + is_current_event=event.is_current() if slug else None, + broadcaster_id=request.GET.get("broadcaster_id"), + building_id=request.GET.get("building_id"), + ) + + if request.POST: + form = EventForm( + request.POST, + instance=event, + user=request.user, + is_current_event=event.is_current() if slug else None, + ) + if form.is_valid(): + event = form.save() + if EMAIL_ON_EVENT_SCHEDULING: + send_email_confirmation(event) + messages.add_message( + request, messages.INFO, _("The changes have been saved.") + ) + return redirect(reverse("live:my_events")) + else: + messages.add_message( + request, + messages.ERROR, + _("One or more errors have been found in the form."), + ) + return render(request, "live/event_edit.html", {"form": form}) + + +@csrf_protect +@login_required(redirect_field_name="referrer") +def event_delete(request, slug=None): + event = get_object_or_404(Event, slug=slug) + + if request.user != event.owner and not ( + request.user.is_superuser or request.user.has_perm("live.delete_event") + ): + messages.add_message(request, messages.ERROR, _("You cannot delete this event.")) + raise PermissionDenied + + form = EventDeleteForm() + + if request.method == "POST": + form = EventDeleteForm(request.POST) + if form.is_valid(): + event.delete() + messages.add_message(request, messages.INFO, _("The event has been deleted.")) + return redirect(reverse("live:my_events")) + else: + messages.add_message( + request, + messages.ERROR, + _("One or more errors have been found in the form."), + ) + + return render(request, "live/event_delete.html", {"event": event, "form": form}) + + +def broadcasters_from_building(request): + building_name = request.GET.get("building") + if not building_name: + return HttpResponseBadRequest() + build = Building.objects.filter(name=building_name).first() + if not build: + return HttpResponseNotFound() + broadcasters = get_available_broadcasters_of_building(request.user, build.id) + + response_data = {} + for broadcaster in broadcasters: + response_data[broadcaster.id] = { + "id": broadcaster.id, + "name": broadcaster.name, + "restricted": broadcaster.is_restricted, + } + return JsonResponse(response_data) + + +def broadcaster_restriction(request): + if request.method == "GET": + # and request.is_ajax(): + + broadcaster_id = request.GET.get("idbroadcaster") + if not broadcaster_id: + return HttpResponseBadRequest() + broadcaster = Broadcaster.objects.get(pk=broadcaster_id) + return JsonResponse({"restricted": broadcaster.is_restricted}) + + return HttpResponseNotAllowed(["GET"]) + + +@csrf_protect +@login_required(redirect_field_name="referrer") +def event_isstreamavailabletorecord(request): + if request.method == "GET" and request.is_ajax(): + broadcaster_id = request.GET.get("idbroadcaster", None) + broadcaster = Broadcaster.objects.get(pk=broadcaster_id) + + if not check_piloting_conf(broadcaster): + return JsonResponse( + { + "available": False, + "recording": False, + "message": "implementation error", + } + ) + + if is_recording(broadcaster, True): + return JsonResponse({"available": True, "recording": True}) + + available = is_available_to_record(broadcaster) + return JsonResponse({"available": available, "recording": False}) + + return HttpResponseNotAllowed(["GET"]) + + +@csrf_protect +@login_required(redirect_field_name="referrer") +def ajax_event_startrecord(request): + if request.method == "POST" and request.is_ajax(): + event_id = request.POST.get("idevent", None) + broadcaster_id = request.POST.get("idbroadcaster", None) + return event_startrecord(event_id, broadcaster_id) + + return HttpResponseNotAllowed(["POST"]) + + +def event_startrecord(event_id, broadcaster_id): + broadcaster = Broadcaster.objects.get(pk=broadcaster_id) + if not check_piloting_conf(broadcaster): + return JsonResponse({"success": False, "message": "implementation error"}) + + if is_recording(broadcaster): + return JsonResponse( + {"success": False, "message": "the broadcaster is already recording"} + ) + + if start_record(broadcaster, event_id): + return JsonResponse({"success": True}) + + return JsonResponse({"success": False, "message": ""}) + + +@csrf_protect +@login_required(redirect_field_name="referrer") +def ajax_event_splitrecord(request): + if request.method == "POST" and request.is_ajax(): + event_id = request.POST.get("idevent", None) + broadcaster_id = request.POST.get("idbroadcaster", None) + + return event_splitrecord(event_id, broadcaster_id) + + return HttpResponseNotAllowed(["POST"]) + + +def event_splitrecord(event_id, broadcaster_id): + broadcaster = Broadcaster.objects.get(pk=broadcaster_id) + + if not check_piloting_conf(broadcaster): + return JsonResponse({"success": False, "error": "implementation error"}) + + if not is_recording(broadcaster, True): + return JsonResponse( + {"success": False, "error": "the broadcaster is not recording"} + ) + + # file infos before split is done + current_record_info = get_info_current_record(broadcaster) + + if split_record(broadcaster): + return event_video_transform( + event_id, + current_record_info.get("currentFile", None), + current_record_info.get("segmentNumber", None), + ) + + return JsonResponse({"success": False, "error": ""}) + + +@csrf_protect +@login_required(redirect_field_name="referrer") +def ajax_event_stoprecord(request): + if request.method == "POST" and request.is_ajax(): + event_id = request.POST.get("idevent", None) + broadcaster_id = request.POST.get("idbroadcaster", None) + return event_stoprecord(event_id, broadcaster_id) + + return HttpResponseNotAllowed(["POST"]) + + +def event_stoprecord(event_id, broadcaster_id): + broadcaster = Broadcaster.objects.get(pk=broadcaster_id) + + if not check_piloting_conf(broadcaster): + return JsonResponse({"success": False, "error": "implementation error"}) + + if not is_recording(broadcaster, True): + return JsonResponse( + {"success": False, "error": "the broadcaster is not recording"} + ) + + current_record_info = get_info_current_record(broadcaster) + + if stop_record(broadcaster): + return event_video_transform( + event_id, + current_record_info.get("currentFile", None), + current_record_info.get("segmentNumber", None), + ) + + return JsonResponse({"success": False, "error": ""}) + + +@login_required(redirect_field_name="referrer") +def ajax_event_info_record(request): + if request.method == "POST" and request.is_ajax(): + event_id = request.POST.get("idevent", None) + broadcaster_id = request.POST.get("idbroadcaster", None) + return event_info_record(event_id, broadcaster_id) + + return HttpResponseNotAllowed(["POST"]) + + +def event_info_record(event_id, broadcaster_id): + broadcaster = Broadcaster.objects.get(pk=broadcaster_id) + + if not check_piloting_conf(broadcaster): + return JsonResponse({"success": False, "error": "implementation error"}) + + if not is_recording(broadcaster): + return JsonResponse( + {"success": False, "error": "the broadcaster is not recording"} + ) + + current_record_info = get_info_current_record(broadcaster) + + if current_record_info.get("segmentDuration") != "": + return JsonResponse( + { + "success": True, + "duration": int( + ( + timedelta(milliseconds=current_record_info.get("segmentDuration")) + ).total_seconds() + ), + } + ) + + return JsonResponse({"success": False, "error": ""}) + + +@csrf_protect +def event_get_video_cards(request): + if request.is_ajax(): + event_id = request.GET.get("idevent", None) + event = Event.objects.get(pk=event_id) + + html = "" + if event.videos.count() > 0: + request.resolver_match.namespace = "" + html = render_to_string( + "live/event_videos.html", {"event": event}, request=request + ) + return JsonResponse({"content": html}) + + return HttpResponseBadRequest() + + +def event_video_transform(event_id, current_file, segment_number): + live_event = Event.objects.get(pk=event_id) + + filename = os.path.basename(current_file) + + dest_file = os.path.join( + settings.MEDIA_ROOT, + VIDEOS_DIR, + live_event.owner.owner.hashkey, + filename, + ) + + dest_path = os.path.join( + VIDEOS_DIR, + live_event.owner.owner.hashkey, + filename, + ) + + # dir creation if not exists + dest_dir_name = os.path.dirname(dest_file) + os.makedirs(dest_dir_name, exist_ok=True) + + try: + checkDirExists(dest_dir_name) + + # file creation if not exists + full_file_name = os.path.join(DEFAULT_EVENT_PATH, filename) + checkFileExists(full_file_name) + + # verif si la taille du fichier d'origine ne bouge plus + checkFileSize(full_file_name) + + # moving the file + os.rename( + full_file_name, + dest_file, + ) + + # verif si la taille du fichier copié ne bouge plus + checkFileSize(dest_file) + + except Exception as exc: + return JsonResponse( + status=500, + data={"success": False, "error": exc}, + ) + + segment = "(" + segment_number + ")" if segment_number else "" + + video = Video.objects.create( + video=dest_path, + title=live_event.title + segment, + owner=live_event.owner, + description=live_event.description + + "
      " + + _("Record the %(start_date)s from %(start_time)s to %(end_time)s") + % { + "start_date": live_event.start_date.strftime("%d/%m/%Y"), + "start_time": live_event.start_time.strftime("%H:%M"), + "end_time": live_event.end_time.strftime("%H:%M"), + }, + is_draft=live_event.is_draft, + type=live_event.type, + ) + if not live_event.is_draft: + video.password = live_event.password + video.is_restricted = live_event.is_restricted + video.restrict_access_to_groups = live_event.restrict_access_to_groups.all() + + video.launch_encode = True + video.save() + + live_event.videos.add(video) + live_event.save() + + videos = live_event.videos.all() + + video_list = {} + for video in videos: + video_list[video.id] = { + "id": video.id, + "slug": video.slug, + "title": video.title, + "get_absolute_url": video.get_absolute_url(), + } + + return JsonResponse({"success": True, "videos": video_list}) + + +def checkFileSize(full_file_name, max_attempt=6): + file_size = os.path.getsize(full_file_name) + size_match = False + + attempt_number = 1 + while not size_match and attempt_number <= max_attempt: + # if attempt_number > 1: + sleep(2) + new_size = os.path.getsize(full_file_name) + if file_size != new_size: + logger.warning( + f"File size of {full_file_name} changing from {file_size} to {new_size}, attempt number {attempt_number} " + ) + file_size = new_size + attempt_number = attempt_number + 1 + if attempt_number == max_attempt: + logger.error(f"File: {full_file_name} is still changing") + raise Exception("checkFileSize aborted") + else: + logger.info(f"Size checked for {full_file_name} : {new_size}") + size_match = True + + +def checkDirExists(dest_dir_name, max_attempt=6): + attempt_number = 1 + while not os.path.isdir(dest_dir_name) and attempt_number <= max_attempt: + logger.warning(f"Dir does not exists, attempt number {attempt_number} ") + + if attempt_number == max_attempt: + logger.error(f"Impossible to create dir {dest_dir_name}") + raise Exception(f"Dir: {dest_dir_name} does not exists and can't be created") + + attempt_number = attempt_number + 1 + sleep(1) + + +def checkFileExists(full_file_name, max_attempt=6): + attempt_number = 1 + while not os.path.exists(full_file_name) and attempt_number <= max_attempt: + logger.warning(f"File does not exists, attempt number {attempt_number} ") + + if attempt_number == max_attempt: + logger.error(f"Impossible to get file {full_file_name}") + raise Exception(f"File: {full_file_name} does not exists") + + attempt_number = attempt_number + 1 + sleep(1) + + +def check_piloting_conf(broadcaster: Broadcaster) -> bool: + impl_class = pilotingInterface.get_piloting_implementation(broadcaster) + if not impl_class: + return False + return impl_class.check_piloting_conf() + + +def start_record(broadcaster: Broadcaster, event_id) -> bool: + impl_class = pilotingInterface.get_piloting_implementation(broadcaster) + if not impl_class: + return False + return impl_class.start(event_id) + + +def split_record(broadcaster: Broadcaster) -> bool: + impl_class = pilotingInterface.get_piloting_implementation(broadcaster) + if not impl_class: + return False + return impl_class.split() + + +def stop_record(broadcaster: Broadcaster) -> bool: + impl_class = pilotingInterface.get_piloting_implementation(broadcaster) + if not impl_class: + return False + return impl_class.stop() + + +def get_info_current_record(broadcaster: Broadcaster) -> dict: + impl_class = pilotingInterface.get_piloting_implementation(broadcaster) + if not impl_class: + return { + "currentFile": "", + "segmentNumber": "", + "outputPath": "", + "segmentDuration": "", + } + return impl_class.get_info_current_record() + + +def is_available_to_record(broadcaster: Broadcaster) -> bool: + impl_class = pilotingInterface.get_piloting_implementation(broadcaster) + if not impl_class: + return False + return impl_class.is_available_to_record() + + +def is_recording(broadcaster: Broadcaster, with_file_check=False) -> bool: + impl_class = pilotingInterface.get_piloting_implementation(broadcaster) + if not impl_class: + return False + return impl_class.is_recording(with_file_check) diff --git a/pod/locale/fr/LC_MESSAGES/django.mo b/pod/locale/fr/LC_MESSAGES/django.mo index 9a7241bdab8baf5eb4b1f3d00f3161bd452ccc87..ecb21ac22284b2834a52335d1f95d98d48c0303c 100644 GIT binary patch delta 34044 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 JuelxF{{hsv`i%ep delta 28091 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
      $-jo0Xy+-e??1^ni`wkzr<&81eJV?< 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\n" "Language-Team: Pod Team pod@esup-portail.org\n" @@ -20,15 +20,15 @@ msgstr "" msgid "Email" msgstr "Courriel" -#: pod/authentication/admin.py:130 pod/authentication/models.py:88 -#: pod/video/admin.py:177 +#: pod/authentication/admin.py:137 pod/authentication/models.py:88 +#: pod/video/admin.py:178 msgid "Establishment" msgstr "Établissement" #: pod/authentication/forms.py:57 pod/authentication/forms.py:58 #: pod/main/templates/navbar.html:32 #: pod/main/templates/navbar_collapse.html:127 pod/podfile/models.py:41 -#: pod/video/forms.py:371 pod/video/models.py:323 +#: pod/video/forms.py:371 pod/video/models.py:324 #: pod/video/templates/videos/filter_aside.html:14 msgid "Users" msgstr "Utilisateurs" @@ -37,60 +37,60 @@ msgstr "Utilisateurs" msgid "local" msgstr "locale" -#: pod/authentication/models.py:38 +#: pod/authentication/models.py:38 pod/custom/settings_local.py:1015 msgid "student" msgstr "étudiant" -#: pod/authentication/models.py:39 +#: pod/authentication/models.py:39 pod/custom/settings_local.py:1016 msgid "faculty" msgstr "université" -#: pod/authentication/models.py:40 +#: pod/authentication/models.py:40 pod/custom/settings_local.py:1017 msgid "staff" msgstr "personnel" -#: pod/authentication/models.py:41 +#: pod/authentication/models.py:41 pod/custom/settings_local.py:1018 msgid "employee" msgstr "employé" -#: pod/authentication/models.py:42 +#: pod/authentication/models.py:42 pod/custom/settings_local.py:1019 msgid "member" msgstr "membre" -#: pod/authentication/models.py:43 +#: pod/authentication/models.py:43 pod/custom/settings_local.py:1020 msgid "affiliate" msgstr "affiliation" -#: pod/authentication/models.py:44 +#: pod/authentication/models.py:44 pod/custom/settings_local.py:1021 msgid "alum" msgstr "ancien étudiant" -#: pod/authentication/models.py:45 +#: pod/authentication/models.py:45 pod/custom/settings_local.py:1022 msgid "library-walk-in" msgstr "lecteur de bibliothèque" -#: pod/authentication/models.py:46 +#: pod/authentication/models.py:46 pod/custom/settings_local.py:1023 msgid "researcher" msgstr "chercheur" -#: pod/authentication/models.py:47 +#: pod/authentication/models.py:47 pod/custom/settings_local.py:1024 msgid "retired" msgstr "retraité" -#: pod/authentication/models.py:48 +#: pod/authentication/models.py:48 pod/custom/settings_local.py:1025 msgid "emeritus" msgstr "emérite" -#: pod/authentication/models.py:49 +#: pod/authentication/models.py:49 pod/custom/settings_local.py:1026 msgid "teacher" msgstr "enseignant" -#: pod/authentication/models.py:50 +#: pod/authentication/models.py:50 pod/custom/settings_local.py:1027 msgid "registered-reader" msgstr "lecteur enregistré" #: pod/authentication/models.py:82 pod/recorder/models.py:307 -#: pod/video/models.py:1868 pod/video/models.py:1921 pod/video/views.py:1633 +#: pod/video/models.py:1883 pod/video/models.py:1936 pod/video/views.py:1633 msgid "Comment" msgstr "Commentaire" @@ -113,7 +113,7 @@ msgstr "Authentification locale" #: pod/authentication/templates/registration/login.html:11 #: pod/authentication/templates/registration/login.html:14 #: pod/authentication/templates/registration/login.html:64 -#: pod/main/templates/navbar.html:133 +#: pod/main/templates/navbar.html:136 msgid "Log in" msgstr "Connexion" @@ -150,9 +150,11 @@ msgstr "Changer votre image de profil" #: pod/bbb/views.py:254 pod/completion/templates/video_caption_maker.html:98 #: pod/enrichment/templates/enrichment/group_enrichment.html:29 #: pod/interactive/templates/interactive/group_interactive.html:27 -#: pod/live/templates/live/live-form.html:10 -#: pod/main/templates/contact_us.html:17 pod/main/tests/test_views.py:99 -#: pod/main/views.py:300 pod/playlist/views.py:217 +#: pod/live/templates/live/event-form.html:10 +#: pod/live/templates/live/event_delete.html:39 pod/live/views.py:489 +#: pod/live/views.py:517 pod/main/templates/contact_us.html:17 +#: pod/main/tests/test_views.py:99 pod/main/views.py:300 +#: pod/playlist/views.py:217 #: pod/recorder/templates/recorder/add_recording.html:34 #: pod/recorder/templates/recorder/record_delete.html:31 #: pod/recorder/views.py:176 pod/recorder/views.py:376 @@ -177,7 +179,9 @@ msgstr "Une ou plusieurs erreurs ont été trouvées dans le formulaire." #: pod/completion/templates/video_caption_maker.html:106 #: pod/enrichment/templates/enrichment/group_enrichment.html:51 #: pod/interactive/templates/interactive/group_interactive.html:49 -#: pod/live/templates/live/live-form.html:25 +#: pod/live/templates/live/event-form.html:25 +#: pod/live/templates/live/event_delete.html:65 +#: pod/live/templates/live/event_edit.html:84 #: pod/main/templates/contact_us.html:40 #: pod/playlist/templates/playlist/playlist_video_list.html:72 #: pod/recorder/templates/recorder/add_recording.html:57 @@ -195,7 +199,7 @@ msgid "Please provide a valid value for this field" msgstr "Veuillez renseigner une valeur valide pour ce champ" #: pod/authentication/templates/userpicture/userpicture.html:54 -#: pod/bbb/templates/bbb/list_meeting.html:56 pod/main/templates/base.html:128 +#: pod/bbb/templates/bbb/list_meeting.html:56 pod/main/templates/base.html:134 #: pod/main/templates/navbar_collapse.html:43 #: pod/main/templates/navbar_collapse.html:91 #: pod/main/templates/navbar_collapse.html:117 @@ -216,7 +220,7 @@ msgstr "Sauvegarder" msgid "Authentication" msgstr "Authentification" -#: pod/bbb/admin.py:47 pod/video/admin.py:227 +#: pod/bbb/admin.py:47 pod/video/admin.py:228 msgid "Encode selected" msgstr "(Ré)encoder la sélection" @@ -265,7 +269,7 @@ msgid "Waiting for encoding" msgstr "En attente d’encodage" #: pod/bbb/models.py:39 pod/bbb/templates/bbb/card.html:26 -#: pod/video/models.py:796 +#: pod/video/models.py:797 msgid "Encoding in progress" msgstr "Encodage en cours" @@ -273,7 +277,7 @@ msgstr "Encodage en cours" msgid "Already published" msgstr "Déjà publié" -#: pod/bbb/models.py:43 pod/video/models.py:871 pod/video/models.py:1771 +#: pod/bbb/models.py:43 pod/video/models.py:879 pod/video/models.py:1786 msgid "Encoding step" msgstr "Étape de l’encodage" @@ -384,7 +388,7 @@ msgstr "Participants" msgid "Start date" msgstr "Date de début" -#: pod/bbb/models.py:178 +#: pod/bbb/models.py:178 pod/live/models.py:342 msgid "Start date of the live." msgstr "Date de début du direct." @@ -425,8 +429,8 @@ msgid "Username / User id, that want to perform the live." msgstr "" "Nom d'utilisateur / ID d'utilisateur, qui souhaite effectuer le direct." -#: pod/bbb/models.py:214 pod/live/models.py:127 pod/recorder/models.py:166 -#: pod/video/forms.py:282 pod/video/models.py:757 +#: pod/bbb/models.py:214 pod/live/models.py:157 pod/live/models.py:393 +#: pod/recorder/models.py:166 pod/video/forms.py:282 pod/video/models.py:758 msgid "Restricted access" msgstr "Accès restreint" @@ -434,7 +438,8 @@ msgstr "Accès restreint" msgid "Is live only accessible to authenticated users?" msgstr "Le direct est-il uniquement accessible aux utilisateurs authentifiés ?" -#: pod/bbb/models.py:222 pod/live/models.py:164 pod/live/models.py:177 +#: pod/bbb/models.py:222 pod/live/admin.py:158 pod/live/models.py:212 +#: pod/live/models.py:251 pod/live/models.py:358 msgid "Broadcaster" msgstr "Diffuseur" @@ -528,7 +533,7 @@ msgstr "par" #: pod/bbb/templates/bbb/list_meeting.html:14 #: pod/bbb/templates/bbb/list_meeting.html:18 #: pod/bbb/templates/bbb/publish_meeting.html:17 -#: pod/main/templates/navbar.html:116 +#: pod/main/templates/navbar.html:119 msgid "My BigBlueButton records" msgstr "Mes sessions BigBlueButton" @@ -604,7 +609,7 @@ msgstr "" #: pod/bbb/templates/bbb/live_list_meeting.html:21 #: pod/bbb/templates/bbb/live_publish_meeting.html:18 #: pod/bbb/templates/bbb/live_publish_meeting.html:83 -#: pod/main/templates/navbar.html:118 +#: pod/main/templates/navbar.html:121 msgid "Perform a BigBlueButton live" msgstr "Réaliser un direct BigBlueButton" @@ -715,6 +720,7 @@ msgstr "Plus" #: pod/bbb/templates/bbb/live_record_list.html:18 #: pod/bbb/templates/bbb/record_list.html:18 +#: pod/live/templates/live/events_list.html:42 #: pod/playlist/templates/playlist/playlist_list.html:39 #: pod/recorder/templates/recorder/record_list.html:18 #: pod/video/templates/videos/video_list.html:18 @@ -768,24 +774,25 @@ msgid "File to import" msgstr "Fichier à importer" #: pod/chapter/models.py:14 pod/completion/models.py:58 -#: pod/enrichment/models.py:112 pod/video/models.py:813 +#: pod/enrichment/models.py:112 pod/video/models.py:814 msgid "video" msgstr "vidéo" #: pod/chapter/models.py:15 pod/enrichment/models.py:113 -#: pod/recorder/models.py:297 pod/video/models.py:584 +#: pod/recorder/models.py:297 pod/video/models.py:585 msgid "title" msgstr "titre" #: pod/chapter/models.py:17 pod/enrichment/models.py:115 -#: pod/video/models.py:586 +#: pod/video/models.py:587 msgid "slug" msgstr "titre court" #: pod/chapter/models.py:21 pod/completion/models.py:255 -#: pod/enrichment/models.py:119 pod/live/models.py:76 pod/playlist/models.py:18 -#: pod/video/models.py:272 pod/video/models.py:424 pod/video/models.py:550 -#: pod/video/models.py:590 pod/video/models.py:646 pod/video/models.py:1997 +#: pod/enrichment/models.py:119 pod/live/models.py:124 +#: pod/playlist/models.py:18 pod/video/models.py:273 pod/video/models.py:425 +#: pod/video/models.py:551 pod/video/models.py:591 pod/video/models.py:647 +#: pod/video/models.py:2012 msgid "" "Used to access this instance, the \"slug\" is a short label containing only " "letters, numbers, underscore or dash top." @@ -796,7 +803,7 @@ msgstr "" #: pod/chapter/models.py:28 pod/chapter/models.py:123 #: pod/chapter/templates/chapter/list_chapter.html:11 -#: pod/completion/models.py:262 +#: pod/completion/models.py:262 pod/live/models.py:346 msgid "Start time" msgstr "Début" @@ -885,10 +892,11 @@ msgstr "Liste des chapitres" #: pod/completion/models.py:249 #: pod/completion/templates/overlay/list_overlay.html:10 #: pod/enrichment/templates/enrichment/list_enrichment.html:10 -#: pod/main/models.py:124 pod/main/views.py:190 pod/playlist/models.py:12 +#: pod/live/models.py:296 pod/main/models.py:124 pod/main/views.py:190 +#: pod/playlist/models.py:12 #: pod/playlist/templates/playlist/playlist_element_list.html:9 -#: pod/video/models.py:258 pod/video/models.py:412 pod/video/models.py:544 -#: pod/video/models.py:633 pod/video/templates/channel/list_theme.html:10 +#: pod/video/models.py:259 pod/video/models.py:413 pod/video/models.py:545 +#: pod/video/models.py:634 pod/video/templates/channel/list_theme.html:10 msgid "Title" msgstr "Titre" @@ -921,6 +929,8 @@ msgstr "Supprimer le chapitre" #: pod/chapter/templates/chapter/list_chapter.html:32 #: pod/enrichment/templates/enrichment/list_enrichment.html:35 +#: pod/live/templates/live/event_delete.html:9 +#: pod/live/templates/live/event_delete.html:71 #: pod/playlist/templates/playlist.html:70 #: pod/podfile/templates/podfile/list_folder_files.html:101 #: pod/podfile/templates/podfile/list_folder_files.html:296 @@ -1016,51 +1026,52 @@ msgstr "Vous ne pouvez chapitrer cette vidéo." msgid "Please correct errors." msgstr "Veuillez corriger les erreurs." -#: pod/completion/models.py:25 +#: pod/completion/models.py:25 pod/custom/settings_local.py:909 msgid "actor" msgstr "acteur" -#: pod/completion/models.py:26 +#: pod/completion/models.py:26 pod/custom/settings_local.py:910 msgid "author" msgstr "auteur" -#: pod/completion/models.py:27 +#: pod/completion/models.py:27 pod/custom/settings_local.py:911 msgid "designer" msgstr "concepteur" -#: pod/completion/models.py:28 +#: pod/completion/models.py:28 pod/custom/settings_local.py:912 msgid "consultant" msgstr "consultant" -#: pod/completion/models.py:29 +#: pod/completion/models.py:29 pod/custom/settings_local.py:913 msgid "contributor" msgstr "contributeur" #: pod/completion/models.py:30 pod/completion/tests/test_views.py:219 +#: pod/custom/settings_local.py:914 msgid "editor" msgstr "éditeur" -#: pod/completion/models.py:31 +#: pod/completion/models.py:31 pod/custom/settings_local.py:915 msgid "speaker" msgstr "intervenant" -#: pod/completion/models.py:32 +#: pod/completion/models.py:32 pod/custom/settings_local.py:916 msgid "soundman" msgstr "preneur de son" -#: pod/completion/models.py:33 +#: pod/completion/models.py:33 pod/custom/settings_local.py:917 msgid "director" msgstr "réalisateur" -#: pod/completion/models.py:34 +#: pod/completion/models.py:34 pod/custom/settings_local.py:918 msgid "writer" msgstr "scénariste" -#: pod/completion/models.py:35 +#: pod/completion/models.py:35 pod/custom/settings_local.py:919 msgid "technician" msgstr "technicien" -#: pod/completion/models.py:36 +#: pod/completion/models.py:36 pod/custom/settings_local.py:920 msgid "voice-over" msgstr "voix-off" @@ -1123,14 +1134,14 @@ msgstr "" #: pod/completion/models.py:124 pod/completion/models.py:171 #: pod/completion/models.py:248 #: pod/completion/templates/video_caption_maker.html:69 -#: pod/enrichment/models.py:322 pod/enrichment/models.py:356 -#: pod/interactive/models.py:18 pod/interactive/models.py:48 -#: pod/playlist/models.py:85 +#: pod/custom/settings_local.py:795 pod/enrichment/models.py:322 +#: pod/enrichment/models.py:356 pod/interactive/models.py:18 +#: pod/interactive/models.py:48 pod/playlist/models.py:85 #: pod/playlist/templates/playlist/playlist_video_list.html:7 #: pod/playlist/templates/playlist/playlist_video_list.html:15 -#: pod/recorder/models.py:90 pod/video/models.py:627 pod/video/models.py:1326 -#: pod/video/models.py:1504 pod/video/models.py:1579 pod/video/models.py:1644 -#: pod/video/models.py:1701 pod/video/models.py:1724 pod/video/models.py:1756 +#: pod/recorder/models.py:90 pod/video/models.py:628 pod/video/models.py:1341 +#: pod/video/models.py:1519 pod/video/models.py:1594 pod/video/models.py:1659 +#: pod/video/models.py:1716 pod/video/models.py:1739 pod/video/models.py:1771 msgid "Video" msgstr "Vidéo" @@ -1235,9 +1246,9 @@ msgstr "bas à gauche" msgid "left" msgstr "gauche" -#: pod/completion/models.py:251 pod/live/models.py:72 pod/playlist/models.py:14 -#: pod/video/models.py:268 pod/video/models.py:421 pod/video/models.py:546 -#: pod/video/models.py:642 pod/video/models.py:1993 +#: pod/completion/models.py:251 pod/live/models.py:120 pod/live/models.py:289 +#: pod/playlist/models.py:14 pod/video/models.py:269 pod/video/models.py:422 +#: pod/video/models.py:547 pod/video/models.py:643 pod/video/models.py:2008 msgid "Slug" msgstr "Titre court" @@ -1245,7 +1256,7 @@ msgstr "Titre court" msgid "Start time of the overlay, in seconds." msgstr "Début de la superposition, en secondes." -#: pod/completion/models.py:267 +#: pod/completion/models.py:267 pod/live/models.py:351 msgid "End time" msgstr "Fin" @@ -1761,6 +1772,16 @@ msgstr "Le fichier n’a pas été enregistré." msgid "Please correct errors" msgstr "Veuillez corriger les erreurs" +#: pod/custom/settings_local.py:796 pod/recorder/models.py:91 +msgid "Audiovideocast" +msgstr "Audiovideocast" + +#: pod/custom/settings_local.py:797 pod/recorder/models.py:92 +#: pod/recorder/templates/recorder/opencast-studio.html:45 +#: pod/recorder/templates/recorder/opencast-studio.html:49 +msgid "Studio" +msgstr "" + #: pod/enrichment/apps.py:7 msgid "Enrichment version" msgstr "Version enrichie" @@ -1781,8 +1802,8 @@ msgstr "Éditer l'enrichissement" #: pod/enrichment/forms.py:33 pod/enrichment/models.py:363 #: pod/interactive/forms.py:12 pod/interactive/models.py:55 -#: pod/podfile/models.py:33 pod/recorder/models.py:176 pod/video/models.py:341 -#: pod/video/models.py:767 +#: pod/live/models.py:172 pod/live/models.py:404 pod/podfile/models.py:33 +#: pod/recorder/models.py:176 pod/video/models.py:342 pod/video/models.py:768 msgid "Groups" msgstr "Groupes" @@ -1824,8 +1845,9 @@ msgstr "Temps de fin de l'affichage de l'enrichissement en secondes." #: pod/enrichment/models.py:141 #: pod/enrichment/templates/enrichment/list_enrichment.html:11 -#: pod/video/forms.py:115 pod/video/models.py:573 pod/video/models.py:653 -#: pod/video/views.py:1675 pod/video_search/templates/search/search.html:83 +#: pod/live/models.py:362 pod/video/forms.py:115 pod/video/models.py:574 +#: pod/video/models.py:654 pod/video/views.py:1675 +#: pod/video_search/templates/search/search.html:83 msgid "Type" msgstr "Type" @@ -2143,12 +2165,15 @@ msgid "Interactive video" msgstr "Video interactive" #: pod/interactive/templates/interactive/video_interactive.html:47 +#: pod/live/templates/live/event-all-info.html:6 #: pod/video/templates/videos/video-all-info.html:6 msgid "Report the video" msgstr "Signaler la vidéo" #: pod/interactive/templates/interactive/video_interactive.html:56 #: pod/interactive/templates/interactive/video_interactive.html:57 +#: pod/live/templates/live/event-all-info.html:16 +#: pod/live/templates/live/event-info.html:9 #: pod/video/templates/videos/video-all-info.html:21 #: pod/video/templates/videos/video-info.html:9 msgid "Summary" @@ -2156,6 +2181,8 @@ msgstr "Description" #: pod/interactive/templates/interactive/video_interactive.html:61 #: pod/interactive/templates/interactive/video_interactive.html:62 +#: pod/live/templates/live/event-all-info.html:22 +#: pod/live/templates/live/event-info.html:22 #: pod/video/templates/videos/video-all-info.html:27 #: pod/video/templates/videos/video-info.html:36 msgid "Infos" @@ -2170,6 +2197,8 @@ msgstr "Téléchargements" #: pod/interactive/templates/interactive/video_interactive.html:73 #: pod/interactive/templates/interactive/video_interactive.html:74 +#: pod/live/templates/live/event-all-info.html:33 +#: pod/live/templates/live/event-info.html:63 #: pod/video/templates/videos/video-all-info.html:44 #: pod/video/templates/videos/video-info.html:199 msgid "Embed/Share" @@ -2202,7 +2231,7 @@ msgid "Add the video to a playlist" msgstr "Ajouter une vidéo à une liste de lecture" #: pod/interactive/templates/interactive/video_interactive.html:101 -#: pod/main/templates/navbar.html:108 +#: pod/main/templates/navbar.html:111 #: pod/playlist/templates/my_playlists.html:10 #: pod/playlist/templates/playlist.html:14 pod/playlist/views.py:48 msgid "My playlists" @@ -2256,16 +2285,80 @@ msgstr "Vous regardez cette vidéo en tant qu'utilisateur anonyme" msgid "You cannot add interactivity to this video." msgstr "Vous ne pouvez éditer cette vidéo interactive." -#: pod/live/forms.py:50 pod/video/forms.py:291 pod/video/forms.py:855 +#: pod/live/admin.py:122 pod/live/models.py:423 pod/recorder/models.py:180 +#: pod/video/models.py:772 +msgid "password" +msgstr "mot de passe" + +#: pod/live/admin.py:127 pod/video/admin.py:75 +msgid "Yes" +msgstr "Oui" + +#: pod/live/admin.py:128 pod/video/admin.py:76 +msgid "No" +msgstr "Non" + +#: pod/live/admin.py:163 +msgid "Auto start admin" +msgstr "Enregistrement auto." + +#: pod/live/admin.py:183 pod/live/models.py:419 pod/video/models.py:784 +#: pod/video/models.py:917 +msgid "Thumbnails" +msgstr "Vignettes" + +#: pod/live/forms.py:59 pod/live/models.py:183 +msgid "Piloting implementation" +msgstr "Implémentation de pilotage" + +#: pod/live/forms.py:60 pod/live/models.py:184 +msgid "Select the piloting implementation for to this broadcaster." +msgstr "" +"Sélectionner l'implémentation de pilotage des enregistrements pour ce " +"diffuseur." + +#: pod/live/forms.py:110 +msgid "End should not be in the past" +msgstr "Un évènement ne peut pas être planifié dans le passé." + +#: pod/live/forms.py:111 pod/live/models.py:275 +msgid "An event cannot be planned in the past" +msgstr "Un évènement ne peut pas être planifié dans le passé." + +#: pod/live/forms.py:114 pod/live/forms.py:115 +msgid "Start should not be after end" +msgstr "L'heure de début doit être avant l'heure de fin" + +#: pod/live/forms.py:132 +msgid "An event is already planned at these dates" +msgstr "Un évenement est déjà planifié à cette date" + +#: pod/live/forms.py:137 pod/video/forms.py:291 pod/video/forms.py:855 msgid "Password" msgstr "Mot de passe" -#: pod/live/models.py:30 pod/live/models.py:70 pod/recorder/models.py:103 +#: pod/live/forms.py:147 pod/live/models.py:71 pod/live/models.py:130 +msgid "Building" +msgstr "Bâtiment" + +#: pod/live/forms.py:154 +msgid "Broadcaster device" +msgstr "Matériel de captation" + +#: pod/live/forms.py:297 pod/recorder/forms.py:75 pod/video/forms.py:864 +msgid "I agree" +msgstr "J'accepte" + +#: pod/live/forms.py:298 +msgid "Delete event cannot be undo" +msgstr "La suppression d'évènement est définitive" + +#: pod/live/models.py:49 pod/live/models.py:118 pod/recorder/models.py:103 msgid "name" msgstr "nom" -#: pod/live/models.py:36 pod/live/templates/live/live.html:153 -#: pod/video/models.py:292 pod/video/models.py:445 +#: pod/live/models.py:55 pod/live/templates/live/direct.html:158 +#: pod/video/models.py:293 pod/video/models.py:446 #: pod/video/templates/channel/channel.html:85 #: pod/video/templates/channel/channel.html:158 #: pod/video/templates/channel/channel.html:168 @@ -2276,161 +2369,271 @@ msgstr "nom" msgid "Headband" msgstr "Bannière" -#: pod/live/models.py:52 pod/live/models.py:82 -msgid "Building" -msgstr "Bâtiment" - -#: pod/live/models.py:53 +#: pod/live/models.py:72 msgid "Buildings" msgstr "Bâtiments" -#: pod/live/models.py:83 pod/recorder/models.py:105 +#: pod/live/models.py:131 pod/recorder/models.py:105 msgid "description" msgstr "description" -#: pod/live/models.py:89 +#: pod/live/models.py:137 msgid "Poster" msgstr "Affiche" -#: pod/live/models.py:91 +#: pod/live/models.py:139 msgid "URL" msgstr "URL" -#: pod/live/models.py:91 +#: pod/live/models.py:139 msgid "Url of the stream" msgstr "Adresse du flux" -#: pod/live/models.py:94 +#: pod/live/models.py:142 msgid "This video will be displayed when there is no live stream." msgstr "Cette vidéo sera affichée lorsqu’il n’y a pas de diffusion en direct." -#: pod/live/models.py:97 +#: pod/live/models.py:145 msgid "Video on hold" msgstr "Vidéo en attente" -#: pod/live/models.py:100 -msgid "Embedded Site URL" -msgstr "Lien du site intégré" - -#: pod/live/models.py:101 -msgid "Url of the embedded site to display" -msgstr "Lien du site à intégrer sous forme d'iframe en bas du live" - -#: pod/live/models.py:106 -msgid "Embedded Site Height" -msgstr "Hauteur du site intégré" - -#: pod/live/models.py:109 -msgid "Height of the embedded site (in pixels)" -msgstr "Hauteur (en pixel) du site intégré sur la page du live" - -#: pod/live/models.py:112 -msgid "Embedded aside Site URL" -msgstr "Lien du site intégré" - -#: pod/live/models.py:113 -msgid "Url of the embedded site to display on aside" -msgstr "Lien du site à intégrer sous forme d'iframe à droite du live" - -#: pod/live/models.py:119 +#: pod/live/models.py:149 msgid "Check if the broadcaster is currently sending stream." msgstr "Cocher la case si le diffuseur envoie un flux." -#: pod/live/models.py:123 +#: pod/live/models.py:153 msgid "Enable viewers count" msgstr "Activer le compteur de spectateurs" -#: pod/live/models.py:124 +#: pod/live/models.py:154 msgid "Enable viewers count on live." msgstr "Active le compteur de spectateurs sur le direct." -#: pod/live/models.py:128 +#: pod/live/models.py:158 msgid "Live is accessible only to authenticated users." msgstr "" "Si cette case est cochée, la page du direct sera accessible uniquement aux " "utilisateurs authentifiés." -#: pod/live/models.py:132 +#: pod/live/models.py:162 msgid "Show in live tab" msgstr "Montrer dans l'onglet Directs" -#: pod/live/models.py:133 +#: pod/live/models.py:163 msgid "Live is accessible from the Live tab" msgstr "" "Si cette case est cochée, la page du direct sera accessible publiquement " "dans l'onglet Directs" -#: pod/live/models.py:137 pod/recorder/models.py:180 pod/video/models.py:771 -msgid "password" -msgstr "mot de passe" - -#: pod/live/models.py:138 -msgid "Viewing this live will not be possible without this password." -msgstr "Voir ce direct n'est pas possible sans mot de passe." - -#: pod/live/models.py:143 +#: pod/live/models.py:166 msgid "Number of viewers" msgstr "Nombre de spectateurs" -#: pod/live/models.py:165 +#: pod/live/models.py:174 +msgid "Select one or more groups who can manage event to this broadcaster." +msgstr "" +"Sélectionner un ou plusieurs groupes qui pourront gérer les évènements de ce " +"diffuseur." + +#: pod/live/models.py:190 +msgid "Piloting configuration parameters" +msgstr "Paramètres de configuration du pilotage" + +#: pod/live/models.py:191 +msgid "Add piloting configuration parameters in Json format." +msgstr "" +"Ajouter les paramètres de configuration nécessaires au pilotage des " +"enregistrements de ce diffuseur au format JSON." + +#: pod/live/models.py:213 msgid "Broadcasters" msgstr "Diffuseurs" -#: pod/live/models.py:174 +#: pod/live/models.py:244 +msgid "Is recording ?" +msgstr "Enregistrement en cours ?" + +#: pod/live/models.py:248 msgid "Viewer" msgstr "Spectateur" -#: pod/live/models.py:175 +#: pod/live/models.py:249 msgid "Viewkey" msgstr "Clé de visionnage" -#: pod/live/models.py:179 +#: pod/live/models.py:253 msgid "Last heartbeat" msgstr "Dernier signal" -#: pod/live/models.py:182 +#: pod/live/models.py:256 msgid "Heartbeat" msgstr "Signal" -#: pod/live/models.py:183 +#: pod/live/models.py:257 msgid "Heartbeats" msgstr "Signaux" -#: pod/live/templates/live/building.html:11 -#: pod/live/templates/live/live.html:22 pod/live/templates/live/live.html:26 -#: pod/live/templates/live/lives.html:15 pod/live/templates/live/lives.html:18 -#: pod/live/templates/live/lives.html:21 pod/main/templates/navbar.html:42 -msgid "Lives" -msgstr "Directs" +#: pod/live/models.py:299 pod/video/models.py:637 +msgid "" +"Please choose a title as short and accurate as possible, reflecting the main " +"subject / context of the content. (max length: 250 characters)" +msgstr "" +"Veuillez choisir un titre aussi court et précis que possible, reflétant le " +"sujet principal / le contexte de ce contenu. (taille maximale : 250 " +"caractères)" -#: pod/live/templates/live/building.html:38 -#: pod/live/templates/live/live.html:164 pod/live/templates/live/lives.html:41 -msgid "Sorry, no lives found" -msgstr "Désolé, aucun direct trouvé" +#: pod/live/models.py:306 pod/main/forms.py:62 pod/main/models.py:177 +#: pod/playlist/models.py:26 pod/video/forms.py:135 pod/video/forms.py:352 +#: pod/video/forms.py:403 pod/video/models.py:279 pod/video/models.py:431 +#: pod/video/models.py:677 pod/video/templates/channel/list_theme.html:11 +msgid "Description" +msgstr "Description" -#: pod/live/templates/live/live-form.html:3 -msgid "This live is protected by password, please fill in and click send." +#: pod/live/models.py:310 pod/video/forms.py:138 pod/video/forms.py:355 +#: pod/video/forms.py:406 pod/video/models.py:283 pod/video/models.py:435 +#: pod/video/models.py:681 +msgid "" +"In this field you can describe your content, add all needed related " +"information, and format the result using the toolbar." msgstr "" -"Ce direct est protégée par un mot de passe, veuillez remplir le formulaire " -"et cliquez sur envoyer." +"Dans ce champ vous pouvez décrire votre contenu, ajouter toutes les " +"informations nécessaires, et mettre en forme le résultat en utilisant la " +"barre d'outils." -#: pod/live/templates/live/live-form.html:8 -#: pod/video/templates/videos/video-form.html:8 -msgid "Password required" -msgstr "Mot de passe requis" +#: pod/live/models.py:319 pod/playlist/models.py:24 +#: pod/playlist/templates/playlist/playlist_element_list.html:10 +#: pod/podfile/models.py:28 pod/video/admin.py:194 pod/video/models.py:658 +#: pod/video_search/templates/search/search.html:71 +msgid "Owner" +msgstr "Propriétaire" -#: pod/live/templates/live/live-form.html:31 -#: pod/main/templates/contact_us.html:46 -#: pod/video/templates/videos/video-form.html:31 -#: pod/video_search/templates/search/search.html:60 -msgid "Send" -msgstr "Envoyer" +#: pod/live/models.py:329 pod/video/forms.py:125 pod/video/models.py:667 +msgid "Additional owners" +msgstr "Propriétaires additionels" + +#: pod/live/models.py:333 +msgid "" +"You can add additional owners to the event. They will have the same rights " +"as you except that they can't delete this event." +msgstr "" +"Vous pouvez ajouter des propriétaires additionnels à la vidéo. Ils auront " +"les mêmes droits que vous sauf qu'ils ne peuvent pas supprimer cette vidéo." + +#: pod/live/models.py:340 pod/video/models.py:688 +msgid "Date of event" +msgstr "Date de l'évènement" + +#: pod/live/models.py:348 +msgid "Start time of the live event." +msgstr "Heure de début du direct." + +#: pod/live/models.py:353 +msgid "End time of the live event." +msgstr "Heure de fin du direct." + +#: pod/live/models.py:359 +msgid "Broadcaster name." +msgstr "Nom du diffuseur" + +#: pod/live/models.py:365 +msgid "Embedded Site URL" +msgstr "Lien du site intégré" + +#: pod/live/models.py:366 +msgid "Url of the embedded site to display" +msgstr "Lien du site à intégrer sous forme d'iframe en bas du live" + +#: pod/live/models.py:371 +msgid "Embedded Site Height" +msgstr "Hauteur du site intégré" + +#: pod/live/models.py:374 +msgid "Height of the embedded site (in pixels)" +msgstr "Hauteur (en pixel) du site intégré sur la page du live" + +#: pod/live/models.py:377 +msgid "Embedded aside Site URL" +msgstr "Lien du site intégré" + +#: pod/live/models.py:378 +msgid "Url of the embedded site to display on aside" +msgstr "Lien du site à intégrer sous forme d'iframe à droite du live" + +#: pod/live/models.py:384 pod/recorder/models.py:157 pod/video/forms.py:273 +#: pod/video/models.py:749 +msgid "Draft" +msgstr "Brouillon" + +#: pod/live/models.py:386 +msgid "" +"If this box is checked, the event will be visible only by you and the " +"additional owners but accessible to anyone having the url link." +msgstr "" +"Si la case est cochée, l'évènement sera visible et accessible uniquement par " +"vous et les propriétaires additionnels mais accessible à quiconque dispose " +"de l'URL." + +#: pod/live/models.py:395 +msgid "" +"If this box is checked, the event will only be accessible to authenticated " +"users." +msgstr "" +"Si cette case est cochée, l'évènement sera uniquement accessible aux " +"utilisateurs authentifiés." + +#: pod/live/models.py:405 +msgid "Select one or more groups who can access to this event" +msgstr "Sélectionner un ou plusieurs groupes qui auront accès à cet événement" + +#: pod/live/models.py:409 +msgid "Auto start" +msgstr "Démarrage automatique de l'enregistrement" + +#: pod/live/models.py:410 +msgid "If this box is checked, the record will start automatically." +msgstr "" +"Si cette case est cochée, l'enregistrement démarrera automatiquement à " +"l'heure indiquée." + +#: pod/live/models.py:424 +msgid "Viewing this event will not be possible without this password." +msgstr "Voir cet évènement n'est pas possible sans mot de passe." + +#: pod/live/models.py:436 +msgid "Event" +msgstr "Evènement" + +#: pod/live/models.py:437 pod/live/templates/live/event.html:26 +#: pod/live/templates/live/events.html:9 pod/live/templates/live/events.html:12 +msgid "Events" +msgstr "Evènements" + +#: pod/live/templates/live/direct.html:22 +#: pod/live/templates/live/direct.html:26 +#: pod/live/templates/live/directs.html:11 +#: pod/live/templates/live/directs_all.html:15 +#: pod/live/templates/live/directs_all.html:18 +#: pod/live/templates/live/directs_all.html:21 +msgid "Lives" +msgstr "Directs" -#: pod/live/templates/live/live.html:102 +#: pod/live/templates/live/direct.html:36 +#: pod/live/templates/live/direct.html:39 +#: pod/live/templates/live/event_edit.html:16 +#: pod/live/templates/live/event_edit.html:21 +#: pod/live/templates/live/event_edit.html:36 +#: pod/live/templates/live/events.html:38 +#: pod/live/templates/live/events.html:41 +#: pod/live/templates/live/my_events.html:50 +#: pod/live/templates/live/my_events.html:53 +msgid "Plan an event" +msgstr "Programmer un évènement" + +#: pod/live/templates/live/direct.html:83 msgid "Send message" msgstr "Envoyer un message" -#: pod/live/templates/live/live.html:104 +#: pod/live/templates/live/direct.html:85 msgid "" "You can send a message (100 characters maximum) to the BigBlueButton " "session. It will be displayed within 15 to 30 seconds on the live video." @@ -2439,88 +2642,529 @@ msgstr "" "BigBlueButton. Il sera affiché dans les 15 à 30 secondes sur la vidéo en " "direct." -#: pod/live/templates/live/live.html:105 +#: pod/live/templates/live/direct.html:86 msgid "Message" msgstr "Message" -#: pod/live/templates/live/live.html:108 +#: pod/live/templates/live/direct.html:89 msgid "You must be authenticated to send a message." msgstr "Vous devez être authentifié pour envoyer un message." -#: pod/live/templates/live/live.html:114 pod/main/templates/aside.html:32 +#: pod/live/templates/live/direct.html:95 pod/main/templates/aside.html:32 #: pod/main/templates/aside.html:53 msgid "Submit" msgstr "Envoyer" -#: pod/live/templates/live/live.html:134 +#: pod/live/templates/live/direct.html:111 +msgid "Recording in progress" +msgstr "Enregistrement en cours" + +#: pod/live/templates/live/direct.html:117 +msgid "No recording in progress" +msgstr "Aucun enregistrement en cours" + +#: pod/live/templates/live/direct.html:126 +#: pod/live/templates/live/events_next.html:7 +msgid "Next events" +msgstr "Prochains évènements" + +#: pod/live/templates/live/direct.html:139 msgid "Manage live" msgstr "Gérer le live" -#: pod/live/templates/live/live.html:136 +#: pod/live/templates/live/direct.html:141 msgid "Edit the live" msgstr "Éditer le live" -#: pod/live/templates/live/live.html:160 pod/live/templates/live/lives.html:39 +#: pod/live/templates/live/direct.html:165 +#: pod/live/templates/live/directs_all.html:39 msgid "no broadcast in progress" msgstr "aucune diffusion actuellement" -#: pod/live/templates/live/live.html:168 pod/live/templates/live/live.html:169 -#: pod/live/templates/live/lives.html:45 pod/live/templates/live/lives.html:46 +#: pod/live/templates/live/direct.html:169 +#: pod/live/templates/live/directs.html:38 +#: pod/live/templates/live/directs_all.html:41 +msgid "Sorry, no lives found" +msgstr "Désolé, aucun direct trouvé" + +#: pod/live/templates/live/direct.html:173 +#: pod/live/templates/live/direct.html:174 +#: pod/live/templates/live/directs_all.html:45 +#: pod/live/templates/live/directs_all.html:46 msgid "access map" msgstr "plan d'accès" -#: pod/live/templates/live/live.html:199 -#: pod/video/templates/videos/video-script.html:28 -msgid "Please use different browser" -msgstr "Utilisez un navigateur compatible" +#: pod/live/templates/live/direct.html:204 +#: pod/live/templates/live/event.html:187 +#: pod/video/templates/videos/video-script.html:28 +msgid "Please use different browser" +msgstr "Utilisez un navigateur compatible" + +#: pod/live/templates/live/direct.html:241 +#: pod/live/templates/live/event.html:229 +msgid "" +"Thank you for watching this streaming live with us. The page will reload " +"automatically within a few seconds to display the video on hold." +msgstr "" +"Merci d’avoir regardé ce streaming en direct avec nous. La page se " +"rechargera automatiquement dans quelques secondes pour afficher la vidéo " +"d’attente." + +#: pod/live/templates/live/direct.html:258 +#: pod/live/templates/live/event.html:253 +msgid "Live not found, displaying the video on hold, retry every 10 seconds" +msgstr "" +"Le direct n’a pas encore commencé, affichage de la vidéo d’attente, nouvelle " +"tentative de lecture dans 10 secondes" + +#: pod/live/templates/live/direct.html:273 +#: pod/live/templates/live/event.html:276 +msgid "Live not found, retry in 10 seconds" +msgstr "" +"Le direct n’a pas encore commencé, nouvelle tentative de lecture dans 10 " +"secondes" + +#: pod/live/templates/live/direct.html:329 +msgid "Message sent" +msgstr "Message envoyé" + +#: pod/live/templates/live/direct.html:330 +msgid "Message not sent: no broadcaster found" +msgstr "Message non envoyé: aucun diffuseur trouvé" + +#: pod/live/templates/live/direct.html:331 +msgid "Message not sent: connection problem (REDIS)" +msgstr "Message non envoyé: problème de connexion (REDIS)" + +#: pod/live/templates/live/directs_all.html:53 +msgid "Sorry, no buildings found" +msgstr "Désolé, aucun bâtiment trouvé" + +#: pod/live/templates/live/event-form.html:3 +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 " +"formulaire et cliquez sur envoyer." + +#: pod/live/templates/live/event-form.html:8 +#: pod/video/templates/videos/video-form.html:8 +msgid "Password required" +msgstr "Mot de passe requis" + +#: pod/live/templates/live/event-form.html:31 +#: pod/live/templates/live/event_edit.html:93 +#: pod/live/templates/live/filter_aside.html:13 +#: pod/main/templates/contact_us.html:46 +#: pod/video/templates/videos/video-form.html:31 +#: pod/video_search/templates/search/search.html:60 +msgid "Send" +msgstr "Envoyer" + +#: pod/live/templates/live/event-iframe.html:66 +#: pod/live/templates/live/event.html:115 +msgid "Event is finished at : " +msgstr "L'évènement s'est terminé à :" + +#: pod/live/templates/live/event-iframe.html:71 +#: pod/live/templates/live/event.html:120 +msgid "The event is scheduled on the : " +msgstr "L'évènement est programmé le : " + +#: pod/live/templates/live/event-iframe.html:72 +#: pod/live/templates/live/event.html:121 +msgid "from" +msgstr "de" + +#: pod/live/templates/live/event-iframe.html:72 +#: pod/live/templates/live/event.html:121 +msgid "to" +msgstr "à" + +#: pod/live/templates/live/event-info.html:26 +msgid "Broadcasted on:" +msgstr "Diffusé de direct le :" + +#: pod/live/templates/live/event-info.html:27 +#, python-format +msgid "%(start_date)s from %(start_time)s to %(end_time)s" +msgstr "%(start_date)s de %(start_time)s à %(end_time)s" + +#: pod/live/templates/live/event-info.html:31 +msgid "Location:" +msgstr "Lieu :" + +#: pod/live/templates/live/event-info.html:36 +#: pod/video/templates/videos/video-info.html:39 +msgid "Added by:" +msgstr "Ajouté par :" + +#: pod/live/templates/live/event-info.html:55 +#: pod/video/templates/videos/video-info.html:120 +msgid "Type:" +msgstr "Type :" + +#: pod/live/templates/live/event-info.html:63 +#: pod/video/templates/videos/video-info.html:199 +#: pod/video/templates/videos/video_edit.html:159 +msgid "Embed/Share (Draft Mode)" +msgstr "Intégrer/Partager (Mode brouillon)" + +#: pod/live/templates/live/event-info.html:68 +msgid "" +"Please note that your event is in draft mode.
      The following links " +"contain a key allowing access. Anyone with this links can access it." +msgstr "" +"Veuillez noter que votre événement est en mode brouillon.
      Les liens " +"suivants contiennent une clé permettant l’accès. Toute personne disposant de " +"ces liens peut y accéder." + +#: pod/live/templates/live/event-info.html:76 +#: pod/video/templates/videos/video-info.html:260 +msgid "Embed in a web page" +msgstr "Intégrer dans une page web" + +#: pod/live/templates/live/event-info.html:78 +#: pod/video/templates/videos/video-info.html:262 +msgid "Copy the content of this text box and paste it in the page:" +msgstr "Copier le contenu de cette boite de texte et coller le sur la page :" + +#: pod/live/templates/live/event-info.html:84 +#: pod/video/templates/videos/video-info.html:202 +msgid "Social Networks" +msgstr "Réseaux sociaux" + +#: pod/live/templates/live/event-info.html:87 +#: pod/live/templates/live/event-info.html:88 +#: pod/live/templates/live/event-info.html:89 pod/main/templates/aside.html:11 +#: pod/main/templates/aside.html:12 pod/main/templates/aside.html:13 +#: pod/video/templates/videos/video-info.html:205 +#: pod/video/templates/videos/video-info.html:206 +#: pod/video/templates/videos/video-info.html:207 +msgid "Share on" +msgstr "Partager sur" + +#: pod/live/templates/live/event-info.html:97 +#: pod/video/templates/videos/video-info.html:267 +msgid "Share the link" +msgstr "Partager le lien" + +#: pod/live/templates/live/event-info.html:99 +#: pod/video/templates/videos/video-info.html:269 +msgid "Use this link to share the video:" +msgstr "Utiliser ce lien pour partager la vidéo :" + +#: pod/live/templates/live/event-info.html:103 +#: pod/video/templates/videos/video-info.html:273 +msgid "QR code for this link:" +msgstr "QR code pour le lien :" + +#: pod/live/templates/live/event.html:24 +#: pod/live/templates/live/event_delete.html:7 +#: pod/live/templates/live/event_edit.html:9 +#: pod/live/templates/live/my_events.html:13 pod/main/templates/navbar.html:106 +msgid "My events" +msgstr "Mes évènements" + +#: pod/live/templates/live/event.html:90 +msgid "Start record" +msgstr "Démarrer l’enregistrement" + +#: pod/live/templates/live/event.html:92 +msgid "Stop record" +msgstr "Stopper l’enregistrement" + +#: pod/live/templates/live/event.html:94 +msgid "Split record" +msgstr "Chapitrer l’enregistrement" + +#: pod/live/templates/live/event.html:129 +msgid "Current event videos" +msgstr "Vidéos de l'évenement" + +#: pod/live/templates/live/event.html:148 +#: pod/live/templates/live/event_edit.html:105 +msgid "Manage event" +msgstr "Gérer l'évènement" + +#: pod/live/templates/live/event.html:151 +#: pod/live/templates/live/event_edit.html:109 +msgid "Edit the event" +msgstr "Éditer l'évènement" + +#: pod/live/templates/live/event.html:158 +#: pod/live/templates/live/event_edit.html:116 +msgid "Delete the event" +msgstr "Supprimer l'évènement" + +#: pod/live/templates/live/event.html:469 +msgid "Recording duration" +msgstr "Temps d’enregistrement" + +#: pod/live/templates/live/event_card.html:21 +#: pod/playlist/templates/playlist/playlist_video_card.html:13 +#: pod/video/templates/videos/card.html:10 +msgid "This content is password protected." +msgstr "Ce contenu est protégé par un mot de passe." + +#: pod/live/templates/live/event_card.html:26 +#: pod/playlist/templates/playlist/playlist_video_card.html:19 +#: pod/video/templates/videos/card.html:16 +msgid "This content is in draft." +msgstr "Ce contenu est en mode brouillon." + +#: pod/live/templates/live/event_card.html:59 +#, python-format +msgid "The %(start_date)s from %(start_time)s to %(end_time)s" +msgstr "Le %(start_date)s de %(start_time)s à %(end_time)s" + +#: pod/live/templates/live/event_delete.html:13 +#: pod/live/templates/live/event_delete.html:17 +#, python-format +msgid "Deleting the event \"%(vtitle)s\"" +msgstr "Supprimer la vidéo « %(vtitle)s »" + +#: pod/live/templates/live/event_delete.html:19 +msgid "View the event" +msgstr "Voir l'évènement" + +#: pod/live/templates/live/event_delete.html:21 +#: pod/live/templates/live/event_delete.html:72 +msgid "Back to the event" +msgstr "Retour à l'évènement" + +#: pod/live/templates/live/event_delete.html:28 +msgid "The event is currently in progress. Deletion is not possible." +msgstr "L'évènement est actuellement en cours. La supression est impossible." + +#: pod/live/templates/live/event_delete.html:32 +msgid "To delete the event, please checked in and click send." +msgstr "Pour supprimer l'évènement, veuillez cocher et cliquer sur envoyer." + +#: pod/live/templates/live/event_delete.html:37 +#: pod/recorder/templates/recorder/record_delete.html:29 +#: pod/video/templates/videos/video_delete.html:30 +msgid "Agree required" +msgstr "Accord requis" + +#: pod/live/templates/live/event_edit.html:14 +#: pod/live/templates/live/event_edit.html:20 +#: pod/live/templates/live/event_edit.html:34 +msgid "Editing the event" +msgstr "Éditer l'évènement" + +#: pod/live/templates/live/event_edit.html:42 +msgid "" +"Access to adding event has been restricted. If you want to add events on the " +"platform, please" +msgstr "" +"L'accès à l'ajout d'évènement est restreint, si vous voulez ajouter des " +"évènements" + +#: pod/live/templates/live/event_edit.html:43 +#: pod/video/templates/videos/add_video.html:31 +#: pod/video/templates/videos/video_edit.html:48 +msgid "contact us" +msgstr "contactez nous" + +#: pod/live/templates/live/event_edit.html:56 +msgid "The event is currently in progress. Editing options are limited." +msgstr "" +"L'évènement est actuellement en cours. Les options d'édition sont limitées." + +#: pod/live/templates/live/event_edit.html:91 +#: pod/live/templates/live/event_edit.html:154 +msgid "Display advanced options" +msgstr "Options avancées" + +#: pod/live/templates/live/event_edit.html:123 +msgid "Event planning" +msgstr "Planification d'un évènement" + +#: pod/live/templates/live/event_edit.html:125 +msgid "" +"You can schedule a live event by selecting a building and a room or " +"recording device." +msgstr "" +"Vous pouvez planifier un évènement en direct en sélectionnant un bâtiment et " +"une salle ou un dispositif de captation." + +#: pod/live/templates/live/event_edit.html:126 +msgid "You will then need to specify a date, a start time and an end time." +msgstr "" +"Vous devrez ensuite préciser une date, une heure de début et une heure de " +"fin. " + +#: pod/live/templates/live/event_edit.html:127 +msgid "" +"Please note that 2 events cannot be scheduled in the same room " +"simultaneously." +msgstr "" +"Attention, 2 évènements ne peuvent pas être programmés dans la même salle " +"simultanément." + +#: pod/live/templates/live/event_edit.html:128 +msgid "Finally, remember to provide as precise a description as possible." +msgstr "Enfin, pensez à renseigner une description la plus précise possible." + +#: pod/live/templates/live/event_edit.html:132 +#: pod/main/templates/contact_us.html:63 +#: pod/video/templates/channel/channel_edit.html:90 +#: pod/video/templates/channel/theme_edit.html:66 +#: pod/video/templates/videos/video_edit.html:186 +msgid "Mandatory fields" +msgstr "Champs obligatoires" + +#: pod/live/templates/live/event_edit.html:134 +#: pod/main/templates/contact_us.html:65 +#: pod/video/templates/channel/channel_edit.html:92 +#: pod/video/templates/channel/theme_edit.html:68 +#: pod/video/templates/videos/video_edit.html:188 +msgid "Fields marked with an asterisk are mandatory." +msgstr "Les champs marqués avec un astérisque sont obligatoires." + +#: pod/live/templates/live/event_edit.html:158 +msgid "Hide advanced options" +msgstr "Cacher les options avancées" + +#: pod/live/templates/live/events.html:28 +#: pod/live/templates/live/my_events.html:16 +#: pod/live/templates/live/my_events.html:47 +#, python-format +msgid "%(counter)s event found" +msgid_plural "%(counter)s events found" +msgstr[0] "%(counter)s évènement trouvé" +msgstr[1] "%(counter)s évènements trouvés" + +#: pod/live/templates/live/events.html:30 +#: pod/live/templates/live/events.html:33 +msgid "Supervise broadcasters" +msgstr "Superviser les diffuseurs" + +#: pod/live/templates/live/events.html:48 +#: pod/live/templates/live/my_events.html:61 +msgid "" +"Please use the thumbnails toolbar which is located under the event on which " +"you want to work with." +msgstr "" +"Veuillez utiliser la barre d'outils située en dessous de la vignette de " +"l'évènement sur lequel vous voulez travailler." + +#: pod/live/templates/live/events_list.html:13 +msgid "Sorry, no event found." +msgstr "Désolé, aucun évènement trouvé." + +#: pod/live/templates/live/events_list.html:26 +#: pod/video/templates/videos/category_modal.html:30 +#: pod/video/templates/videos/paginator.html:7 +msgid "Previous page" +msgstr "Page précédente" + +#: pod/live/templates/live/events_list.html:36 +#: pod/video/templates/videos/category_modal.html:35 +#: pod/video/templates/videos/paginator.html:21 +msgid "Next page" +msgstr "Page suivante" + +#: pod/live/templates/live/events_next.html:14 +msgid "Show all events" +msgstr "Afficher tous les évènements" + +#: pod/live/templates/live/filter_aside.html:8 +#: pod/video/templates/videos/filter_aside.html:8 +#: pod/video/templates/videos/filter_aside_category.html:6 +msgid "Filters" +msgstr "Filtres" + +#: pod/live/templates/live/filter_aside.html:12 +#: pod/main/templates/aside.html:42 pod/main/templates/aside.html:48 +#: pod/main/templates/navbar.html:37 pod/video/models.py:575 +#: pod/video/templates/videos/filter_aside.html:36 +msgid "Types" +msgstr "Types" + +#: pod/live/templates/live/my_events.html:45 +msgid "No event found" +msgstr "Aucun évènement trouvé" -#: pod/live/templates/live/live.html:236 +#: pod/live/templates/live/my_events.html:59 msgid "" -"Thank you for watching this streaming live with us. The page will reload " -"automatically within a few seconds to display the video on hold." +"You have not planned any event yet, please use the \"Plan an event\" button " +"to add one" msgstr "" -"Merci d’avoir regardé ce streaming en direct avec nous. La page se " -"rechargera automatiquement dans quelques secondes pour afficher la vidéo " -"d’attente." +"Vous n'avez pas encore planifié d'évènement, veuillez utiliser le bouton " +"\"Programmer un évènement\" pour en créer un" -#: pod/live/templates/live/live.html:253 -msgid "Live not found, displaying the video on hold, retry every 10 seconds" -msgstr "" -"Le direct n’a pas encore commencé, affichage de la vidéo d’attente, nouvelle " -"tentative de lecture dans 10 secondes" +#: pod/live/templates/live/my_events.html:63 +msgid "Coming events" +msgstr "Evènements à venir" -#: pod/live/templates/live/live.html:268 -msgid "Live not found, retry in 10 seconds" -msgstr "" -"Le direct n’a pas encore commencé, nouvelle tentative de lecture dans 10 " -"secondes" +#: pod/live/templates/live/my_events.html:66 +msgid "Past events" +msgstr "Evènements passés" -#: pod/live/templates/live/live.html:324 -msgid "Message sent" -msgstr "Message envoyé" +#: pod/live/utils.py:50 +#, python-format +msgid "Registration of event #%(content_id)s" +msgstr "Programmation de l'évènement #%(content_id)s" -#: pod/live/templates/live/live.html:325 -msgid "Message not sent: no broadcaster found" -msgstr "Message non envoyé: aucun diffuseur trouvé" +#: pod/live/utils.py:58 pod/live/utils.py:79 pod/video/utils.py:199 +#: pod/video/utils.py:224 pod/video/utils.py:292 pod/video/utils.py:317 +msgid "Hello," +msgstr "Bonjour," -#: pod/live/templates/live/live.html:326 -msgid "Message not sent: connection problem (REDIS)" -msgstr "Message non envoyé: problème de connexion (REDIS)" +#: pod/live/utils.py:60 pod/live/utils.py:81 +#, python-format +msgid "" +"You have just scheduled a new event called “%(content_title)s” in date of " +"%(start_date)s from %(start_time)s to %(end_time)s on video server : " +"%(url_event)s). You can find the other sharing options in the dedicated tab." +msgstr "" +"Vous venez de programmer un nouvel évènement direct intitulé " +"“%(content_title)s” pour le %(start_date)s de %(start_time)s à %(end_time)s " +"sur le serveur vidéo de l'Université de Lorraine : %(url_event)s). Vous " +"pouvez retrouver les autres options de partage dans l'onglet dédié." -#: pod/live/templates/live/lives.html:53 -msgid "Sorry, no buildings found" -msgstr "Désolé, aucun bâtiment trouvé" +#: pod/live/utils.py:70 pod/live/utils.py:91 pod/video/utils.py:207 +#: pod/video/utils.py:236 pod/video/utils.py:300 pod/video/utils.py:329 +msgid "Regards." +msgstr "Cordialement." + +#: pod/live/utils.py:74 pod/live/utils.py:146 pod/video/utils.py:210 +#: pod/video/utils.py:240 pod/video/utils.py:303 pod/video/utils.py:333 +msgid "Post by:" +msgstr "Posté par :" -#: pod/live/views.py:58 +#: pod/live/views.py:69 pod/live/views.py:95 pod/live/views.py:104 msgid "You cannot view this page." -msgstr "Vous ne pouvez pas éditer cette image." +msgstr "Vous ne pouvez pas accèder à cette page." -#: pod/live/views.py:97 +#: pod/live/views.py:270 +msgid "You cannot watch this event." +msgstr "Vous ne pouvez pas accèder à cet évènement en direct." + +#: pod/live/views.py:285 #: pod/playlist/templates/playlist/playlist_video_list.html:73 #: pod/video/views.py:871 msgid "The password is incorrect." msgstr "Le mot de passe est incorrect." +#: pod/live/views.py:482 pod/video/views.py:361 pod/video/views.py:947 +msgid "The changes have been saved." +msgstr "Les modifications ont été sauvegardées." + +#: pod/live/views.py:502 +msgid "You cannot delete this event." +msgstr "Vous ne pouvez pas supprimer cet évènement." + +#: pod/live/views.py:511 +msgid "The event has been deleted." +msgstr "L'évènement a été supprimé." + +#: pod/live/views.py:787 +#, python-format +msgid "Record the %(start_date)s from %(start_time)s to %(end_time)s" +msgstr "Enregistré le %(start_date)s de %(start_time)s à %(end_time)s" + #: pod/lti/templates/lti_provider/assignment.html:28 msgid "Your browser does not support iframes" msgstr "Votre navigateur ne supporte pas les iframes" @@ -2562,8 +3206,8 @@ msgid "Other (please specify)" msgstr "Autre (spécifiez)" #: pod/main/forms.py:50 pod/podfile/models.py:25 pod/podfile/models.py:117 -#: pod/podfile/templates/podfile/home_content.html:32 pod/video/models.py:1497 -#: pod/video/models.py:1572 pod/video/models.py:1637 +#: pod/podfile/templates/podfile/home_content.html:32 pod/video/models.py:1512 +#: pod/video/models.py:1587 pod/video/models.py:1652 msgid "Name" msgstr "Nom" @@ -2575,13 +3219,6 @@ msgstr "Sujet" msgid "Please choose a subject related to your request" msgstr "Veuillez choisir un sujet en lien avec votre requête" -#: pod/main/forms.py:62 pod/main/models.py:177 pod/playlist/models.py:26 -#: pod/video/forms.py:135 pod/video/forms.py:352 pod/video/forms.py:403 -#: pod/video/models.py:278 pod/video/models.py:430 pod/video/models.py:676 -#: pod/video/templates/channel/list_theme.html:11 -msgid "Description" -msgstr "Description" - #: pod/main/forms.py:63 msgid "Provide a full description for your request" msgstr "Renseignez une description complète pour votre requête" @@ -3291,47 +3928,33 @@ msgstr "Administration de Pod" msgid "Share" msgstr "Partager" -#: pod/main/templates/aside.html:11 pod/main/templates/aside.html:12 -#: pod/main/templates/aside.html:13 -#: pod/video/templates/videos/video-info.html:205 -#: pod/video/templates/videos/video-info.html:206 -#: pod/video/templates/videos/video-info.html:207 -msgid "Share on" -msgstr "Partager sur" - #: pod/main/templates/aside.html:21 pod/main/templates/aside.html:27 -#: pod/recorder/models.py:213 pod/video/forms.py:181 pod/video/models.py:614 -#: pod/video/models.py:716 pod/video/templates/videos/filter_aside.html:56 +#: pod/recorder/models.py:213 pod/video/forms.py:181 pod/video/models.py:615 +#: pod/video/models.py:717 pod/video/templates/videos/filter_aside.html:56 msgid "Disciplines" msgstr "Disciplines" -#: pod/main/templates/aside.html:42 pod/main/templates/aside.html:48 -#: pod/main/templates/navbar.html:37 pod/video/models.py:574 -#: pod/video/templates/videos/filter_aside.html:36 -msgid "Types" -msgstr "Types" - #: pod/main/templates/aside.html:62 pod/recorder/models.py:210 -#: pod/video/forms.py:172 pod/video/models.py:713 +#: pod/video/forms.py:172 pod/video/models.py:714 #: pod/video/templates/videos/filter_aside.html:76 #: pod/video_search/templates/search/search.html:95 msgid "Tags" msgstr "Mots clés" -#: pod/main/templates/base.html:79 +#: pod/main/templates/base.html:82 msgid "Breadcrumb" msgstr "Fil d’Ariane" -#: pod/main/templates/base.html:82 +#: pod/main/templates/base.html:85 #: pod/playlist/templates/playlist_player.html:16 msgid "Home" msgstr "Accueil" -#: pod/main/templates/base.html:87 +#: pod/main/templates/base.html:90 msgid "Toggle side Menu" msgstr "Afficher/masquer le menu latéral" -#: pod/main/templates/base.html:143 +#: pod/main/templates/base.html:149 msgid "" "We use third party cookies to personalize content, manage session and " "analyze site traffic." @@ -3340,11 +3963,11 @@ msgstr "" "droite, mode sombre ou dyslexie etc.), gérer la session (authentification) " "et analyser le trafic du site (pour certaines instances)." -#: pod/main/templates/base.html:145 +#: pod/main/templates/base.html:151 msgid "Learn more" msgstr "En savoir plus" -#: pod/main/templates/base.html:147 +#: pod/main/templates/base.html:153 msgid "I understand" msgstr "J’ai compris" @@ -3352,20 +3975,6 @@ msgstr "J’ai compris" msgid "Back to the previous page" msgstr "Retour à la page précédente" -#: pod/main/templates/contact_us.html:63 -#: pod/video/templates/channel/channel_edit.html:90 -#: pod/video/templates/channel/theme_edit.html:66 -#: pod/video/templates/videos/video_edit.html:186 -msgid "Mandatory fields" -msgstr "Champs obligatoires" - -#: pod/main/templates/contact_us.html:65 -#: pod/video/templates/channel/channel_edit.html:92 -#: pod/video/templates/channel/theme_edit.html:68 -#: pod/video/templates/videos/video_edit.html:188 -msgid "Fields marked with an asterisk are mandatory." -msgstr "Les champs marqués avec un astérisque sont obligatoires." - #: pod/main/templates/footer.html:29 msgid "" "Esup Portal: Community of French higher education establishments for digital " @@ -3447,10 +4056,14 @@ msgid "Toggle navigation" msgstr "Basculer le menu" #: pod/main/templates/navbar.html:21 pod/recorder/models.py:223 -#: pod/video/forms.py:254 pod/video/models.py:356 pod/video/models.py:726 +#: pod/video/forms.py:254 pod/video/models.py:357 pod/video/models.py:727 msgid "Channels" msgstr "Chaînes" +#: pod/main/templates/navbar.html:42 +msgid "Live events" +msgstr "Évènements en direct" + #: pod/main/templates/navbar.html:63 pod/main/templates/navbar.html:65 #: pod/main/templates/navbar_collapse.html:136 #: pod/main/templates/navbar_collapse.html:137 @@ -3479,7 +4092,7 @@ msgstr "Ajouter votre image de profil" msgid "Video Record" msgstr "Enregistreur" -#: pod/main/templates/navbar.html:106 +#: pod/main/templates/navbar.html:109 #: pod/video/templates/channel/channel_edit.html:17 #: pod/video/templates/channel/my_channels.html:6 #: pod/video/templates/channel/my_channels.html:9 @@ -3487,27 +4100,27 @@ msgstr "Enregistreur" msgid "My channels" msgstr "Mes chaînes" -#: pod/main/templates/navbar.html:110 pod/podfile/templates/podfile/home.html:6 +#: pod/main/templates/navbar.html:113 pod/podfile/templates/podfile/home.html:6 #: pod/podfile/templates/podfile/home.html:10 pod/podfile/views.py:103 msgid "My files" msgstr "Mes fichiers" -#: pod/main/templates/navbar.html:113 +#: pod/main/templates/navbar.html:116 #: pod/recorder/templates/recorder/claim_record.html:12 #: pod/recorder/templates/recorder/record_delete.html:7 #: pod/recorder/templates/recorder/record_delete.html:19 msgid "Claim a record" msgstr "Revendiquer un enregistrement" -#: pod/main/templates/navbar.html:124 +#: pod/main/templates/navbar.html:127 msgid "Log out" msgstr "Déconnexion" -#: pod/main/templates/navbar.html:161 pod/main/templates/navbar.html:163 +#: pod/main/templates/navbar.html:164 pod/main/templates/navbar.html:166 msgid "Dark mode" msgstr "Mode sombre" -#: pod/main/templates/navbar.html:172 pod/main/templates/navbar.html:174 +#: pod/main/templates/navbar.html:175 pod/main/templates/navbar.html:177 msgid "Dyslexia mode" msgstr "Police ‘Open Dyslexic’" @@ -3561,18 +4174,11 @@ msgstr "Lien" msgid "your message untitled" msgstr "votre message intitulé" -#: pod/playlist/models.py:24 -#: pod/playlist/templates/playlist/playlist_element_list.html:10 -#: pod/podfile/models.py:28 pod/video/admin.py:193 pod/video/models.py:657 -#: pod/video_search/templates/search/search.html:71 -msgid "Owner" -msgstr "Propriétaire" - #: pod/playlist/models.py:30 msgid "Short description of the playlist." msgstr "Courte description de la liste de lecture." -#: pod/playlist/models.py:33 pod/video/models.py:331 +#: pod/playlist/models.py:33 pod/video/models.py:332 msgid "Visible" msgstr "Visible" @@ -3696,7 +4302,7 @@ msgid "Thumbnail" msgstr "Vignette" #: pod/playlist/templates/playlist/playlist_element_list.html:11 -#: pod/video/feeds.py:201 pod/video/models.py:785 pod/video/models.py:932 +#: pod/video/feeds.py:201 pod/video/models.py:786 pod/video/models.py:940 msgid "Duration" msgstr "Durée" @@ -3724,16 +4330,6 @@ msgstr "Lancer" msgid "Sorry, no playlist found" msgstr "Désolé, aucune liste de lecture trouvée" -#: pod/playlist/templates/playlist/playlist_video_card.html:13 -#: pod/video/templates/videos/card.html:10 -msgid "This content is password protected." -msgstr "Ce contenu est protégé par un mot de passe." - -#: pod/playlist/templates/playlist/playlist_video_card.html:19 -#: pod/video/templates/videos/card.html:16 -msgid "This content is in draft." -msgstr "Ce contenu est en mode brouillon." - #: pod/playlist/templates/playlist/playlist_video_card.html:24 #: pod/video/templates/videos/card.html:21 msgid "This content is chaptered." @@ -3991,77 +4587,63 @@ msgstr "Supprimer cet enregistrement" msgid "If checked, record will be deleted instead of saving it" msgstr "Si coché, l'enregistrement sera supprimé et non ajouté" -#: pod/recorder/forms.py:75 pod/video/forms.py:864 -msgid "I agree" -msgstr "J'accepte" - #: pod/recorder/forms.py:76 msgid "Delete this record cannot be undo" msgstr "La suppression d'un enregistrement est définitive" -#: pod/recorder/models.py:32 pod/video/models.py:72 pod/video/views.py:147 +#: pod/recorder/models.py:32 pod/video/models.py:73 pod/video/views.py:147 msgid "None / All" msgstr "Aucun / Tous" -#: pod/recorder/models.py:33 pod/video/models.py:73 pod/video/views.py:148 +#: pod/recorder/models.py:33 pod/video/models.py:74 pod/video/views.py:148 msgid "Bachelor’s Degree" msgstr "Licence" -#: pod/recorder/models.py:34 pod/video/models.py:74 pod/video/views.py:149 +#: pod/recorder/models.py:34 pod/video/models.py:75 pod/video/views.py:149 msgid "Master’s Degree" msgstr "Master" -#: pod/recorder/models.py:35 pod/video/models.py:75 pod/video/views.py:150 +#: pod/recorder/models.py:35 pod/video/models.py:76 pod/video/views.py:150 msgid "Doctorate" msgstr "Doctorat" -#: pod/recorder/models.py:36 pod/video/models.py:76 pod/video/views.py:151 +#: pod/recorder/models.py:36 pod/video/models.py:77 pod/video/views.py:151 msgid "Other" msgstr "Autre" -#: pod/recorder/models.py:43 pod/video/forms.py:201 pod/video/models.py:88 +#: pod/recorder/models.py:43 pod/video/forms.py:201 pod/video/models.py:89 msgid "Attribution 4.0 International (CC BY 4.0)" msgstr "Attribution 4.0 International (CC BY 4.0)" -#: pod/recorder/models.py:46 pod/video/forms.py:208 pod/video/models.py:91 +#: pod/recorder/models.py:46 pod/video/forms.py:208 pod/video/models.py:92 msgid "Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)" msgstr "Attribution - Pas de Modification 4.0 International (CC BY-ND 4.0)" -#: pod/recorder/models.py:51 pod/video/forms.py:218 pod/video/models.py:96 +#: pod/recorder/models.py:51 pod/video/forms.py:218 pod/video/models.py:97 msgid "" "Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)" msgstr "" "Attribution - Pas d'Utilisation Commerciale - Pas de Modification 4.0 " "International (CC BY-NC-ND 4.0)" -#: pod/recorder/models.py:57 pod/video/forms.py:228 pod/video/models.py:102 +#: pod/recorder/models.py:57 pod/video/forms.py:228 pod/video/models.py:103 msgid "Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)" msgstr "" "Attribution - Pas d’Utilisation Commerciale 4.0 International (CC BY-NC 4.0)" -#: pod/recorder/models.py:62 pod/video/forms.py:238 pod/video/models.py:107 +#: pod/recorder/models.py:62 pod/video/forms.py:238 pod/video/models.py:108 msgid "" "Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)" msgstr "" "Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes " "Conditions 4.0 International (CC BY-NC-SA 4.0)" -#: pod/recorder/models.py:66 pod/video/forms.py:248 pod/video/models.py:111 +#: pod/recorder/models.py:66 pod/video/forms.py:248 pod/video/models.py:112 msgid "Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)" msgstr "" "Attribution - Partage dans les Mêmes Conditions 4.0 International (CC BY-SA " "4.0)" -#: pod/recorder/models.py:91 -msgid "Audiovideocast" -msgstr "Audiovideocast" - -#: pod/recorder/models.py:92 -#: pod/recorder/templates/recorder/opencast-studio.html:45 -#: pod/recorder/templates/recorder/opencast-studio.html:49 -msgid "Studio" -msgstr "" - #: pod/recorder/models.py:108 msgid "Address IP" msgstr "Adresse IP" @@ -4110,11 +4692,7 @@ msgstr "" msgid "Video type by default." msgstr "Type par défaut des vidéos." -#: pod/recorder/models.py:157 pod/video/forms.py:273 pod/video/models.py:748 -msgid "Draft" -msgstr "Brouillon" - -#: pod/recorder/models.py:159 pod/video/models.py:750 +#: pod/recorder/models.py:159 pod/video/models.py:751 msgid "" "If this box is checked, the video will be visible and accessible only by you " "and the additional owners." @@ -4122,7 +4700,7 @@ msgstr "" "Si la case est cochée, la vidéo sera visible et accessible uniquement par " "vous et les propriétaires additionnels." -#: pod/recorder/models.py:168 pod/video/models.py:759 +#: pod/recorder/models.py:168 pod/video/models.py:760 msgid "" "If this box is checked, the video will only be accessible to authenticated " "users." @@ -4130,48 +4708,48 @@ msgstr "" "Si cette case est cochée, la vidéo sera uniquement accessible aux " "utilisateurs authentifiés." -#: pod/recorder/models.py:177 pod/video/models.py:768 +#: pod/recorder/models.py:177 pod/video/models.py:769 msgid "Select one or more groups who can access to this video" msgstr "Sélectionner un ou plusieurs groupes qui auront accès à cette vidéo" -#: pod/recorder/models.py:181 pod/video/models.py:772 +#: pod/recorder/models.py:181 pod/video/models.py:773 msgid "Viewing this video will not be possible without this password." msgstr "Voir cette vidéo n'est pas possible sans mot de passe." -#: pod/recorder/models.py:187 pod/video/forms.py:154 pod/video/models.py:690 +#: pod/recorder/models.py:187 pod/video/forms.py:154 pod/video/models.py:691 #: pod/video/templates/videos/filter_aside.html:100 #: pod/video_search/templates/search/search.html:146 msgid "University course" msgstr "Cursus universitaire" -#: pod/recorder/models.py:191 pod/video/forms.py:157 pod/video/models.py:694 +#: pod/recorder/models.py:191 pod/video/forms.py:157 pod/video/models.py:695 msgid "Select an university course as audience target of the content." msgstr "" "Sélectionner un cursus universitaire qui convient à l'audience de ce contenu." -#: pod/recorder/models.py:194 pod/video/forms.py:168 pod/video/models.py:697 +#: pod/recorder/models.py:194 pod/video/forms.py:168 pod/video/models.py:698 #: pod/video_search/templates/search/search.html:133 msgid "Main language" msgstr "Langue principale" -#: pod/recorder/models.py:198 pod/video/forms.py:169 pod/video/models.py:701 +#: pod/recorder/models.py:198 pod/video/forms.py:169 pod/video/models.py:702 msgid "Select the main language used in the content." msgstr "Sélectionner la langue principalement utilisée dans ce contenu." -#: pod/recorder/models.py:201 pod/video/forms.py:312 pod/video/models.py:704 +#: pod/recorder/models.py:201 pod/video/forms.py:312 pod/video/models.py:705 #: pod/video/templates/videos/add_video.html:50 #: pod/video/templates/videos/add_video.html:94 msgid "Transcript" msgstr "Transcrire" -#: pod/recorder/models.py:203 pod/video/models.py:706 +#: pod/recorder/models.py:203 pod/video/models.py:707 #: pod/video/templates/videos/add_video.html:51 msgid "Check this box if you want to transcript the audio. (beta version)" msgstr "" "Cocher cette case si vous voulez transcrire l’audio (version bêta, voir aide " "dans la colonne de droite)" -#: pod/recorder/models.py:207 pod/video/models.py:710 +#: pod/recorder/models.py:207 pod/video/models.py:711 msgid "" "Separate tags with spaces, enclose the tags consist of several words in " "quotation marks." @@ -4179,47 +4757,47 @@ msgstr "" "Séparer les mots clés par des espaces, écrire les mots clés en plusieurs " "mots entre guillemets." -#: pod/recorder/models.py:216 pod/video/forms.py:195 pod/video/models.py:719 +#: pod/recorder/models.py:216 pod/video/forms.py:195 pod/video/models.py:720 msgid "Licence" msgstr "Licence" -#: pod/recorder/models.py:227 pod/video/forms.py:254 pod/video/models.py:539 -#: pod/video/models.py:730 +#: pod/recorder/models.py:227 pod/video/forms.py:254 pod/video/models.py:540 +#: pod/video/models.py:731 msgid "Themes" msgstr "Thèmes" #: pod/recorder/models.py:230 pod/video/forms.py:189 pod/video/forms.py:262 -#: pod/video/models.py:733 pod/video/models.py:1989 +#: pod/video/models.py:734 pod/video/models.py:2004 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" "Maintenez \"Control\", ou \"Command\" sur un Mac, pour en sélectionner " "plusieurs." -#: pod/recorder/models.py:234 pod/video/models.py:737 +#: pod/recorder/models.py:234 pod/video/models.py:738 msgid "allow downloading" msgstr "autoriser le téléchargement" -#: pod/recorder/models.py:236 pod/video/models.py:739 +#: pod/recorder/models.py:236 pod/video/models.py:740 msgid "Check this box if you to allow downloading of the encoded files" msgstr "" "Cocher cette case si vous voulez autoriser le téléchargement des fichiers " "encodés" -#: pod/recorder/models.py:239 pod/video/models.py:742 +#: pod/recorder/models.py:239 pod/video/models.py:743 msgid "video 360" msgstr "vidéo 360" -#: pod/recorder/models.py:241 pod/video/models.py:744 +#: pod/recorder/models.py:241 pod/video/models.py:745 msgid "Check this box if you want to use the 360 player for the video" msgstr "" "Cocher cette case si vous voulez utiliser le lecteur 360 pour cette vidéo" -#: pod/recorder/models.py:244 pod/video/models.py:803 +#: pod/recorder/models.py:244 pod/video/models.py:804 msgid "Disable comment" msgstr "Désactiver les commentaires" -#: pod/recorder/models.py:245 pod/video/models.py:804 +#: pod/recorder/models.py:245 pod/video/models.py:805 msgid "Allows you to turn off all comments on this video." msgstr "Vous permet de désactiver les commentaires sur cette vidéo." @@ -4281,8 +4859,8 @@ msgstr "Fichier source de la vidéo publiée." msgid "File size" msgstr "Taille du fichier" -#: pod/recorder/models.py:366 pod/video/models.py:685 pod/video/models.py:1812 -#: pod/video/models.py:1869 +#: pod/recorder/models.py:366 pod/video/models.py:686 pod/video/models.py:1827 +#: pod/video/models.py:1884 msgid "Date added" msgstr "Date d'ajout" @@ -4399,11 +4977,6 @@ msgstr "Retour aux réclamations d’enregistrement" msgid "To delete the record, please checked in and click send." msgstr "Pour supprimer la vidéo, veuillez cocher et cliquer sur envoyer." -#: pod/recorder/templates/recorder/record_delete.html:29 -#: pod/video/templates/videos/video_delete.html:30 -msgid "Agree required" -msgstr "Accord requis" - #: pod/recorder/views.py:93 msgid "Recorder should be indicated." msgstr "L'enregistreur doit être indiqué." @@ -4482,23 +5055,15 @@ msgstr "Enregistreur Studio non trouvé." msgid "Pod Administration" msgstr "Administration de Pod" -#: pod/video/admin.py:69 +#: pod/video/admin.py:70 msgid "Encoded?" msgstr "Encodé ?" -#: pod/video/admin.py:74 -msgid "Yes" -msgstr "Oui" - -#: pod/video/admin.py:75 -msgid "No" -msgstr "Non" - -#: pod/video/admin.py:235 +#: pod/video/admin.py:236 msgid "Transcript selected" msgstr "Transcription selectionnée" -#: pod/video/admin.py:323 pod/video/forms.py:371 pod/video/models.py:313 +#: pod/video/admin.py:324 pod/video/forms.py:371 pod/video/models.py:314 msgid "Owners" msgstr "Propriétaires" @@ -4506,7 +5071,7 @@ msgstr "Propriétaires" msgid "File field" msgstr "Fichier" -#: pod/video/forms.py:91 pod/video/models.py:630 +#: pod/video/forms.py:91 pod/video/models.py:631 #: pod/video/templates/videos/add_video.html:86 msgid "You can send an audio or video file." msgstr "Vous pouvez envoyer un fichier audio ou vidéo." @@ -4554,10 +5119,6 @@ msgstr "" "n’apparaît pas dans la liste, veuillez temporairement choisir \"Autres\" et " "contactez-nous pour expliquer vos besoins." -#: pod/video/forms.py:125 pod/video/models.py:666 -msgid "Additional owners" -msgstr "Propriétaires additionels" - #: pod/video/forms.py:128 msgid "" "In this field you can select and add additional owners to the video. These " @@ -4568,16 +5129,6 @@ msgstr "" "supplémentaires à la vidéo. Ces autres propriétaires auront les mêmes droits " "que vous sauf qu'ils ne peuvent pas supprimer cette vidéo." -#: pod/video/forms.py:138 pod/video/forms.py:355 pod/video/forms.py:406 -#: pod/video/models.py:282 pod/video/models.py:434 pod/video/models.py:680 -msgid "" -"In this field you can describe your content, add all needed related " -"information, and format the result using the toolbar." -msgstr "" -"Dans ce champ vous pouvez décrire votre contenu, ajouter toutes les " -"informations nécessaires, et mettre en forme le résultat en utilisant la " -"barre d'outils." - #: pod/video/forms.py:145 msgid "Date of the event field" msgstr "Date de l'évènement" @@ -4682,11 +5233,11 @@ msgstr "" "Vous aurez probablement à améliorer ce fichier en utilisant l’outil de sous-" "titrage accessible sur la page de complétion de la video." -#: pod/video/forms.py:362 pod/video/models.py:305 +#: pod/video/forms.py:362 pod/video/models.py:306 msgid "Extra style" msgstr "Style supplémentaire" -#: pod/video/forms.py:362 pod/video/models.py:295 +#: pod/video/forms.py:362 pod/video/models.py:296 msgid "Background color" msgstr "Couleur d'arrière-plan" @@ -4838,24 +5389,24 @@ msgstr "Les vidéos archivées sur Pod" msgid "In %(deadline)s days" msgstr "Dans %(deadline)s jours" -#: pod/video/models.py:145 +#: pod/video/models.py:146 msgid "Private -" msgstr "Privé (moi seulement)" -#: pod/video/models.py:145 +#: pod/video/models.py:146 msgid "Private +" msgstr "Partagé avec le propriétaire de la vidéo" -#: pod/video/models.py:145 +#: pod/video/models.py:146 msgid "Public" msgstr "Public" -#: pod/video/models.py:157 +#: pod/video/models.py:158 #, python-format msgid "%(app)s version" msgstr "Version %(app)s" -#: pod/video/models.py:262 pod/video/models.py:415 +#: pod/video/models.py:263 pod/video/models.py:416 msgid "" "Please choose a title as short and accurate as possible, reflecting the main " "subject / context of the content.(max length: 100 characters)" @@ -4864,7 +5415,7 @@ msgstr "" "sujet principal / le contexte de votre contenu. (taille maximale : 100 " "caractères)" -#: pod/video/models.py:300 +#: pod/video/models.py:301 msgid "" "The background color for your channel. You can use the format #. i.e.: " "#ff0000 for red" @@ -4872,57 +5423,49 @@ msgstr "" "La couleur d'arrière-plan pour votre chaîne. Vous pouvez utiliser le format " "#. i.e.: #ff0000 pour le rouge" -#: pod/video/models.py:308 +#: pod/video/models.py:309 msgid "The style will be added to your channel to show it" msgstr "Le style sera ajouté à votre chaîne" -#: pod/video/models.py:333 +#: pod/video/models.py:334 msgid "" "If checked, the channel appear in a list of available channels on the " "platform." msgstr "" "Si coché, la chaîne apparaîtra dans la liste des chaînes de la plateforme." -#: pod/video/models.py:344 +#: pod/video/models.py:345 msgid "Select one or more groups who can upload video to this channel." msgstr "" "Sélectionnez un ou plusieurs groupes qui peuvent ajouter des vidéos à cette " "chaîne." -#: pod/video/models.py:347 +#: pod/video/models.py:348 msgid "Additionals channels tab" msgstr "Onglet additionnel de chaînes" -#: pod/video/models.py:355 pod/video/models.py:449 +#: pod/video/models.py:356 pod/video/models.py:450 #: pod/video_search/templates/search/search.html:120 msgid "Channel" msgstr "Chaîne" -#: pod/video/models.py:409 +#: pod/video/models.py:410 msgid "Theme parent" msgstr "Thème parent" -#: pod/video/models.py:538 +#: pod/video/models.py:539 msgid "Theme" msgstr "Thème" -#: pod/video/models.py:560 pod/video/models.py:600 +#: pod/video/models.py:561 pod/video/models.py:601 msgid "Icon" msgstr "Icone" -#: pod/video/models.py:613 pod/video_search/templates/search/search.html:107 +#: pod/video/models.py:614 pod/video_search/templates/search/search.html:107 msgid "Discipline" msgstr "Discipline" -#: pod/video/models.py:636 -msgid "" -"Please choose a title as short and accurate as possible, reflecting the main " -"subject / context of the content. (max length: 250 characters)" -msgstr "" -"Choissez un titre aussi court et précis que possible, reflétant le sujet " -"principal / le contexte de ce contenu. (taille maximale : 250 caractères)" - -#: pod/video/models.py:670 +#: pod/video/models.py:671 msgid "" "You can add additional owners to the video. They will have the same rights " "as you except that they can't delete this video." @@ -4930,67 +5473,63 @@ msgstr "" "Vous pouvez ajouter des propriétaires additionnels à la vidéo. Ils auront " "les mêmes droits que vous sauf qu'ils ne peuvent pas supprimer cette vidéo." -#: pod/video/models.py:687 -msgid "Date of event" -msgstr "Date de l'évènement" - -#: pod/video/models.py:783 pod/video/models.py:909 -msgid "Thumbnails" -msgstr "Vignettes" - -#: pod/video/models.py:787 +#: pod/video/models.py:788 msgid "Overview" msgstr "Vue d'ensemble" -#: pod/video/models.py:798 +#: pod/video/models.py:799 msgid "Is Video" msgstr "Est une vidéo" -#: pod/video/models.py:800 +#: pod/video/models.py:801 msgid "Date to delete" msgstr "Date de suppression" -#: pod/video/models.py:814 +#: pod/video/models.py:815 msgid "videos" msgstr "vidéos" -#: pod/video/models.py:860 +#: pod/video/models.py:861 msgid "Sum of view" msgstr "Somme des vues" -#: pod/video/models.py:943 +#: pod/video/models.py:868 +msgid "Sum of view of last 6 months (180 days)" +msgstr "" + +#: pod/video/models.py:951 msgid "Is the video encoded?" msgstr "Est-ce que la vidéo est encodée ?" -#: pod/video/models.py:1279 +#: pod/video/models.py:1294 msgid "Update Owner" msgstr "Mettre à jour le propriétaire" -#: pod/video/models.py:1280 +#: pod/video/models.py:1295 msgid "Update Owners" msgstr "Mettre à jour les propriétaires" -#: pod/video/models.py:1327 +#: pod/video/models.py:1342 msgid "Date" msgstr "Date" -#: pod/video/models.py:1328 +#: pod/video/models.py:1343 msgid "Number of view" msgstr "Nombre de vues" -#: pod/video/models.py:1336 +#: pod/video/models.py:1351 msgid "View count" msgstr "Nombre de vues" -#: pod/video/models.py:1337 +#: pod/video/models.py:1352 msgid "View counts" msgstr "Statistiques" -#: pod/video/models.py:1342 +#: pod/video/models.py:1357 msgid "resolution" msgstr "résolution" -#: pod/video/models.py:1346 +#: pod/video/models.py:1361 msgid "" "Please use the only format x. i.e.: 640x360 or 1280x720 or " "1920x1080." @@ -4998,12 +5537,12 @@ msgstr "" "Veuillez utiliser le format LxH. soit: 640x360 ou 1280x720 " "ou 1920x1080." -#: pod/video/models.py:1352 pod/video/models.py:1443 pod/video/models.py:1450 +#: pod/video/models.py:1367 pod/video/models.py:1458 pod/video/models.py:1465 msgid "minrate" msgstr "débit minimum" -#: pod/video/models.py:1355 pod/video/models.py:1364 pod/video/models.py:1373 -#: pod/video/models.py:1382 +#: pod/video/models.py:1370 pod/video/models.py:1379 pod/video/models.py:1388 +#: pod/video/models.py:1397 msgid "" "Please use the only format k. i.e.: 300k or 600k or " "1000k." @@ -5011,179 +5550,179 @@ msgstr "" "Veuillez utiliser le format XXk soit: 300k ou 600k ou " "1000k." -#: pod/video/models.py:1361 pod/video/models.py:1411 pod/video/models.py:1422 +#: pod/video/models.py:1376 pod/video/models.py:1426 pod/video/models.py:1437 msgid "bitrate video" msgstr "débit binaire de la vidéo" -#: pod/video/models.py:1370 pod/video/models.py:1431 pod/video/models.py:1438 +#: pod/video/models.py:1385 pod/video/models.py:1446 pod/video/models.py:1453 msgid "maxrate" msgstr "débit maximum" -#: pod/video/models.py:1379 pod/video/models.py:1468 pod/video/models.py:1479 +#: pod/video/models.py:1394 pod/video/models.py:1483 pod/video/models.py:1494 msgid "bitrate audio" msgstr "débit binaire de l'audio" -#: pod/video/models.py:1387 +#: pod/video/models.py:1402 msgid "Make a MP4 version" msgstr "Faire une version MP4" -#: pod/video/models.py:1400 pod/video/models.py:1505 +#: pod/video/models.py:1415 pod/video/models.py:1520 msgid "rendition" msgstr "rendu" -#: pod/video/models.py:1401 +#: pod/video/models.py:1416 msgid "renditions" msgstr "rendus" -#: pod/video/models.py:1501 pod/video/models.py:1576 pod/video/models.py:1641 +#: pod/video/models.py:1516 pod/video/models.py:1591 pod/video/models.py:1656 msgid "Please use the only format in encoding choices:" msgstr "Merci de sélectionner un format d’encodage valide :" -#: pod/video/models.py:1507 pod/video/models.py:1581 pod/video/models.py:1646 +#: pod/video/models.py:1522 pod/video/models.py:1596 pod/video/models.py:1661 msgid "Format" msgstr "Format" -#: pod/video/models.py:1511 pod/video/models.py:1585 pod/video/models.py:1650 +#: pod/video/models.py:1526 pod/video/models.py:1600 pod/video/models.py:1665 msgid "Please use the only format in format choices:" msgstr "Merci de sélectionner un format valide :" -#: pod/video/models.py:1515 pod/video/models.py:1589 pod/video/models.py:1654 +#: pod/video/models.py:1530 pod/video/models.py:1604 pod/video/models.py:1669 msgid "encoding source file" msgstr "fichier source d'encodage" -#: pod/video/models.py:1540 +#: pod/video/models.py:1555 msgid "Encoding video" msgstr "Encodage vidéo" -#: pod/video/models.py:1541 +#: pod/video/models.py:1556 msgid "Encoding videos" msgstr "Encodage des vidéos" -#: pod/video/models.py:1604 +#: pod/video/models.py:1619 msgid "Encoding audio" msgstr "Encodage audio" -#: pod/video/models.py:1605 +#: pod/video/models.py:1620 msgid "Encoding audios" msgstr "Encodage des audios" -#: pod/video/models.py:1660 +#: pod/video/models.py:1675 msgid "Video Playlist" msgstr "Liste de lecture" -#: pod/video/models.py:1661 +#: pod/video/models.py:1676 msgid "Video Playlists" msgstr "Listes de lecture" -#: pod/video/models.py:1715 +#: pod/video/models.py:1730 msgid "Encoding log" msgstr "Journal de l'encodage" -#: pod/video/models.py:1716 +#: pod/video/models.py:1731 msgid "Encoding logs" msgstr "Journaux des encodages" -#: pod/video/models.py:1727 pod/video/models.py:1736 +#: pod/video/models.py:1742 pod/video/models.py:1751 msgid "Video version" msgstr "Version de la video" -#: pod/video/models.py:1732 +#: pod/video/models.py:1747 msgid "Video default version." msgstr "Version par défaut de la vidéo." -#: pod/video/models.py:1737 +#: pod/video/models.py:1752 msgid "Video versions" msgstr "Versions de la video" -#: pod/video/models.py:1772 +#: pod/video/models.py:1787 msgid "Encoding steps" msgstr "Etapes de l'encodage" -#: pod/video/models.py:1781 pod/video/models.py:1792 pod/video/models.py:1810 +#: pod/video/models.py:1796 pod/video/models.py:1807 pod/video/models.py:1825 #: pod/video/views.py:1647 msgid "Note" msgstr "Note" -#: pod/video/models.py:1793 +#: pod/video/models.py:1808 msgid "Notes" msgstr "Notes" -#: pod/video/models.py:1804 +#: pod/video/models.py:1819 msgid "Note availability level" msgstr "Niveau de disponibilité de la note" -#: pod/video/models.py:1808 +#: pod/video/models.py:1823 msgid "Select an availability level for the note." msgstr "Sélectionner le niveau de disponibilité de la note." -#: pod/video/models.py:1811 +#: pod/video/models.py:1826 msgid "Timestamp" msgstr "Horodatage" -#: pod/video/models.py:1813 pod/video/models.py:1870 +#: pod/video/models.py:1828 pod/video/models.py:1885 msgid "Date modified" msgstr "Date de modification" -#: pod/video/models.py:1816 +#: pod/video/models.py:1831 msgid "Advanced Note" msgstr "Note avancée" -#: pod/video/models.py:1817 +#: pod/video/models.py:1832 msgid "Advanced Notes" msgstr "Notes avancées" -#: pod/video/models.py:1862 +#: pod/video/models.py:1877 msgid "Comment availability level" msgstr "Niveau de disponibilité du commentaire" -#: pod/video/models.py:1866 +#: pod/video/models.py:1881 msgid "Select an availability level for the comment." msgstr "Sélectionnez un niveau de disponibilité pour le commentaire." -#: pod/video/models.py:1873 +#: pod/video/models.py:1888 msgid "Note comment" msgstr "Commentaire de la note" -#: pod/video/models.py:1874 +#: pod/video/models.py:1889 msgid "Note comments" msgstr "Commentaires de notes" -#: pod/video/models.py:1888 +#: pod/video/models.py:1903 msgid "Date for deletion" msgstr "Date pour la suppression" -#: pod/video/models.py:1890 pod/video/models.py:1986 +#: pod/video/models.py:1905 pod/video/models.py:2001 #: pod/video/templates/videos/video.html:80 #: pod/video/templates/videos/videos.html:8 #: pod/video/templates/videos/videos.html:17 msgid "Videos" msgstr "Vidéos" -#: pod/video/models.py:1893 +#: pod/video/models.py:1908 msgid "Video to delete" msgstr "Vidéo à supprimer" -#: pod/video/models.py:1894 +#: pod/video/models.py:1909 msgid "Videos to delete" msgstr "Vidéos à supprimer" -#: pod/video/models.py:1922 pod/video/templates/videos/video-comment.html:9 +#: pod/video/models.py:1937 pod/video/templates/videos/video-comment.html:9 msgid "Comments" msgstr "Commentaires" -#: pod/video/models.py:1966 +#: pod/video/models.py:1981 msgid "Vote" msgstr "Vote" -#: pod/video/models.py:1967 +#: pod/video/models.py:1982 msgid "Votes" msgstr "Votes" -#: pod/video/models.py:1975 pod/video/templates/videos/category_modal.html:13 +#: pod/video/models.py:1990 pod/video/templates/videos/category_modal.html:13 msgid "Category title" msgstr "Titre catégorie" -#: pod/video/models.py:1978 +#: pod/video/models.py:1993 msgid "" "Please choose a title as short and accurate as possible, reflecting the main " "subject / context of the content.(max length : 100 characters)" @@ -5192,11 +5731,11 @@ msgstr "" "sujet principal / le contexte de votre contenu. (taille maximale : 100 " "caractères)" -#: pod/video/models.py:2012 pod/video/templates/videos/category_modal.html:7 +#: pod/video/models.py:2027 pod/video/templates/videos/category_modal.html:7 msgid "Category" msgstr "Catégorie" -#: pod/video/models.py:2013 +#: pod/video/models.py:2028 #: pod/video/templates/videos/filter_aside_category.html:14 msgid "Categories" msgstr "Catégories" @@ -5359,11 +5898,6 @@ msgid "" msgstr "" "L'accès à l'ajout de vidéo est restreint, si vous voulez ajouter des vidéos" -#: pod/video/templates/videos/add_video.html:31 -#: pod/video/templates/videos/video_edit.html:48 -msgid "contact us" -msgstr "contactez nous" - #: pod/video/templates/videos/add_video.html:34 msgid "Media upload" msgstr "Téléverser un média" @@ -5431,16 +5965,6 @@ msgstr "titre catégorie" msgid "Check the videos to add in the category" msgstr "Cocher les vidéos à ajouter dans la catégorie" -#: pod/video/templates/videos/category_modal.html:30 -#: pod/video/templates/videos/paginator.html:7 -msgid "Previous page" -msgstr "Page précédente" - -#: pod/video/templates/videos/category_modal.html:35 -#: pod/video/templates/videos/paginator.html:21 -msgid "Next page" -msgstr "Page suivante" - #: pod/video/templates/videos/category_modal.html:44 msgid "Save category" msgstr "Sauvegarder catégorie" @@ -5493,11 +6017,6 @@ msgstr "Sélectionner/Désélectionner toutes les vidéos" msgid "Submit changes" msgstr "Soumettre les changements" -#: pod/video/templates/videos/filter_aside.html:8 -#: pod/video/templates/videos/filter_aside_category.html:6 -msgid "Filters" -msgstr "Filtres" - #: pod/video/templates/videos/filter_aside.html:16 msgid "Filter by user" msgstr "Filtrer par utilisateur" @@ -5635,10 +6154,6 @@ msgstr "Mots clés :" msgid "No information available" msgstr "Aucune information disponible" -#: pod/video/templates/videos/video-info.html:39 -msgid "Added by:" -msgstr "Ajouté par :" - #: pod/video/templates/videos/video-info.html:52 msgid "Additional owner(s):" msgstr "Propriétaire(s) additionnel(s) :" @@ -5681,10 +6196,6 @@ msgstr "Afficher les details des statistiques de visualisation" msgid "Show details views" msgstr "Afficher les details de visualisation" -#: pod/video/templates/videos/video-info.html:120 -msgid "Type:" -msgstr "Type :" - #: pod/video/templates/videos/video-info.html:125 msgid "Main language:" msgstr "Langue principale :" @@ -5713,15 +6224,6 @@ msgstr "Fichier audio :" msgid "Document:" msgstr "Document :" -#: pod/video/templates/videos/video-info.html:199 -#: pod/video/templates/videos/video_edit.html:159 -msgid "Embed/Share (Draft Mode)" -msgstr "Intégrer/Partager (Mode brouillon)" - -#: pod/video/templates/videos/video-info.html:202 -msgid "Social Networks" -msgstr "Réseaux sociaux" - #: pod/video/templates/videos/video-info.html:216 msgid "" "Please note that your video is in draft mode.
      The following links " @@ -5775,29 +6277,9 @@ msgstr "" "Utilisez ces liens dans une conférence BigBlueButton ou une activité vidéo " "interactive H5P :" -#: pod/video/templates/videos/video-info.html:260 -msgid "Embed in a web page" -msgstr "Intégrer dans une page web" - -#: pod/video/templates/videos/video-info.html:262 -msgid "Copy the content of this text box and paste it in the page:" -msgstr "Copier le contenu de cette boite de texte et coller le sur la page :" - -#: pod/video/templates/videos/video-info.html:267 -msgid "Share the link" -msgstr "Partager le lien" - -#: pod/video/templates/videos/video-info.html:269 -msgid "Use this link to share the video:" -msgstr "Utiliser ce lien pour partager la vidéo :" - -#: pod/video/templates/videos/video-info.html:273 -msgid "QR code for this link:" -msgstr "QR code pour le lien :" - #: pod/video/templates/videos/video-info.html:274 msgid "Warning, it use google API" -msgstr "Attention, utilise l'API Google" +msgstr "" #: pod/video/templates/videos/video.html:24 #: pod/video/templates/videos/video.html:37 @@ -6041,6 +6523,58 @@ msgstr "Modifier la période de visualisation" msgid "Show view statistics for all videos" msgstr "Afficher les statistiques de visualisation de toutes les vidéos" +#: pod/video/templatetags/video_tags.py:93 +#, python-format +msgid "tags_for_model tag was given an invalid model: %s" +msgstr "" + +#: pod/video/templatetags/video_tags.py:133 +#, python-format +msgid "%s tag requires either three or five arguments" +msgstr "" + +#: pod/video/templatetags/video_tags.py:136 +#: pod/video/templatetags/video_tags.py:197 +#, python-format +msgid "second argument to %s tag must be 'as'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:140 +#: pod/video/templatetags/video_tags.py:202 +#, python-format +msgid "if given, fourth argument to %s tag must be 'with'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:144 +#, python-format +msgid "if given, fifth argument to %s tag must be 'counts'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:193 +#, python-format +msgid "%s tag requires either three or between five and seven arguments" +msgstr "" + +#: pod/video/templatetags/video_tags.py:217 +#, python-format +msgid "%(tag)s tag was given an invalid option: '%(option)s'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:225 +#, python-format +msgid "%(tag)s tag was given a badly formatted option: '%(option)s'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:243 +#, python-format +msgid "%(tag)s tag's '%(option)s' option was not a valid integer: '%(value)s'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:258 +#, python-format +msgid "%(tag)s tag's '%(option)s' option was not a valid choice: '%(value)s'" +msgstr "" + #: pod/video/tests/test_models.py:222 pod/video/tests/test_models.py:275 #: pod/video/translation.py:12 pod/video/translation.py:17 msgid "-- sorry, no translation provided --" @@ -6051,11 +6585,6 @@ msgstr "-- désolé, aucune traduction fournie --" msgid "Transcripting #%(content_id)s completed" msgstr "La transcription #%(content_id)s est terminée" -#: pod/video/utils.py:199 pod/video/utils.py:224 pod/video/utils.py:292 -#: pod/video/utils.py:317 -msgid "Hello," -msgstr "Bonjour," - #: pod/video/utils.py:201 pod/video/utils.py:226 #, python-format msgid "" @@ -6070,16 +6599,6 @@ msgstr "" msgid "You will find it here:" msgstr "Vous la retrouverez ici :" -#: pod/video/utils.py:207 pod/video/utils.py:236 pod/video/utils.py:300 -#: pod/video/utils.py:329 -msgid "Regards." -msgstr "Cordialement." - -#: pod/video/utils.py:210 pod/video/utils.py:240 pod/video/utils.py:303 -#: pod/video/utils.py:333 -msgid "Post by:" -msgstr "Posté par :" - #: pod/video/utils.py:212 pod/video/utils.py:242 pod/video/utils.py:305 #: pod/video/utils.py:335 msgid "the:" @@ -6103,10 +6622,6 @@ msgstr "" msgid "You cannot edit this channel." msgstr "Vous ne pouvez éditer cette chaîne." -#: pod/video/views.py:361 pod/video/views.py:947 -msgid "The changes have been saved." -msgstr "Les modifications ont été sauvegardées." - #: pod/video/views.py:887 msgid "You cannot watch this video." msgstr "Vous ne pouvez pas voir cette vidéo." @@ -6270,6 +6785,19 @@ msgstr "Désolé, la recherche de vidéos est limitée à 500 pages maximum." msgid "Search results" msgstr "Résultats de la recherche" +#, fuzzy +#~| msgid "" +#~| "Please choose a title as short and accurate as possible, reflecting the " +#~| "main subject / context of the content. (max length: 250 characters)" +#~ msgid "" +#~ "Please choose a title as short and accurate as possible, reflecting the " +#~ "main subject / context of the content.(max length: 250 characters)" +#~ msgstr "" +#~ "Veuillez choisir un titre aussi court et précis que possible, reflétant " +#~ "le sujet principal / le contexte de ce contenu (taille maximale : 250 " +#~ "caractères)." + +#, python-format #~ msgid "Added by: %(owner)s" #~ msgstr "Ajouté par : %(owner)s" diff --git a/pod/locale/fr/LC_MESSAGES/djangojs.mo b/pod/locale/fr/LC_MESSAGES/djangojs.mo index 67a01bd91f72ca8dd493694009da82f9d860aada..4655df722ed7d5f8c118e38b8b3ea3bafa4ca8e3 100644 GIT binary patch delta 3353 zcmZwIdu$X{6vy#fS}9btKoM$1d#jW}DO*4(Zzy2v1BCJrD4=#bJEbGbF3iqSP+?6# zL!ya^n;MORQ4$puF*ZK_K-3g58U-;CgAc@n7>#IRqA@Wdet)|oHtM8jK6hSs?mhR+ z_S}kdt(j9pi@FV4h8Rf7z34jRCY?d=j{IAz|!HavobSaUJ=;y4_PjW`^a;8g6y zI(!QI<54WePs8(H;Y7W*WRNitF3iO7xE^cqF|5PG;rXBOQjRMx$#)pRYdD^aS7K{u z7gllHje7Cx$h*uTqzm%_mf>euu4(^3V+bdT=v|W@j-#;>hhj69Vk@dc57og&)XaCH zX4Z}BXE*X^4zL@Khfotbi{&_i&Mw0VSf;c$)6h&-qdMA-5xhV2Rn&uLa0#Bn8eGiV zHGnR5qi`4MzWw3(Bd8_)4D0ci@OTuX)KbmGj2>J?Ln+*b)%bXL{0^#vuhHTkI03ID zEgHZgti;WznLdK5iNmM~oJ37*0O`6M>q47R{kD~nf2DLAC-^fv*B7-%1P%k`y ztdcp6TB4s&Yx!Gf5pPwI4nd`MI1a=*)Y>dr$XwauA7?FQ) z1kU5Q4##2=C*xx{36J44>_@Me zC`EHZ7obwR5@!}LBUEaiV!Cbk465VeEAll{ikiuIyb9;yE!c*$@FmoeokcCluQ(0Q z<8*!h)5vE7Cjy*`FQ8_8CbWXeoXv3yDs>N`2CxUUG|!-l@GM@9gBXRDq8@eMGNca8 z8sx(<3DkhMW39gbhr<(ZqDFWe$Ke;KwJfH03g3)GU5_BEV5XuvYC+8~hD!Yg)crfK z5xY?nJAoQtsm<5gB&^SHVi^rmVRoQ8I)s|pdpHj-p>oKUX~DG^M`h@J)Bw(-Ixb_U zHEuu+@HXU~CXJ2QjjEXwSc6JaM&Ioi8oICoXW+fapE<~mzRhXW1O521n1!`C8#|Fd z^DH|R=Z8qSnDeOXmsjVDY!$LPW)o^a4`Cf1s3!kfqc6i7&!AF#E<7$~I!c{IJ`~f6 zy1o&W;=98052A{AFIqf+y6@xg{O72z<6Bhm4PiD*a6t|Ezm~>sPH05OQ8W1ySKvfG z`jxl~^_iVUbyP~aG}GxwotovS^P5mh@&sOlFQb<3HB|qfpo;K3swhh{Jf)Fejk*xQ zg*Y2Eum@1J@fJ4X?>HZ4P@(EDgIcl|L%&1I@hC2qU=uQF(}J2<7Z&65sDWi(p)rWY zQPcpwK&AEvEWmk05m8UvLtIBJCbZo^D2;{0Dnc3SM`&XO%~B#wY$hfWF+!V`U>(6v zNyc>0P_=67PXxJEe%INjP0{9xN2kgOrTI=m^|64^mLg^n9-$wh>j_Ox|2wpi9Mh&o zxQ*5KuT5(;CD+RD5}<-h5-fRctw>02YY)9URF$sH61Niif1+(Uq13Bxe4>%irr(q% zVldIF_p_wNA-V`{O8?Eo7Ge{zk(f(po%I8w_0wml!qcWt_}?49U1m+6lNa}CUrKup zv6je0*{xQCxSgmX{@&s=Tw)P%4WWwHww`FtwJ5Am6`KldS$Hh;%{LJI9OSk;*e?v* z+mSD8X|B2la!^BTB~*mB5vqRu%Bf2GZg+7ofY?CX7+$yuw-NJo+;d;a-lA+%>643X z(CMXYdvc2fr_*)TyRkY>y4Kz7CIZ{##p5=ah;Oy+u8lg5n@V}@an}Y(8%=XtBJiAO z;Kpn^<@%{e_M6gKRqcK<8grtl!1Zm)4Qxl!4_}k^;xR9=uBT#fVc~?OZYuCSM-R8V zPLvLE@9q0EWAZQD)ibhedqL0F!v+?viYBb<`*fRhoV4%LLoDsnvF-D4%ElRnjYJ~m zzqcn7eQ&iLZs2rsYxdg_KbK6ncKoCUJ8{bN$vxkcpX{IgrSkK&%RKA2LEu`ZMQT!k zb&^hJ?>^VYYkK!>?tL!dZcw@sE;II{eKWNwT@y`jvFX5zdnrQ#R(>nAg!kBGSIym}KX;w!Qc1z*FC5ZuWTX=$<)My@gX3x5vG8(IDyh`Pb%> z7+wDVmyDZT LQ)#pB*1qu63ZPA=A zSIeumT(7ezw}>i;W4H@8}frMM8=(1vm{Sp$j#@4(I%FOy&4GYQTSR9NxpxIOdU< zl{kjuI@EO`7a(@${^C&0I;b6Roxp*CiV=8wYiepd%7NZ6zN3FaX zwX!pQF~Y7 zI0sdfHKHfn2fP(?W#*>+Qp%*FUZbohTmDb(+OL)FZE)D08)P!VUN&M!cnui--(YIY3y=_s`!oEgXC zhDz;OmQ|1ar~!&tu4*rOfT|)m_4Wo??-L%3Fr74 zCh7UVOJ_1)#Iqc2K@n2eW*(}jRv=k5^{9b1qE@&AmHOSN>knZSo))qiW*| z%)=|F$26JQ=zKZOzy=H{b??#P8gm-;gR3|TQ}}W=E=IjDwxf#hFj5xgvh)3aNZFVQ zE+Uy`DQZF+FdyGUZOtjvb$zJJUK&sSb>apmD0DL*C-y}aDy8F4=cl8pwi+#5g&JU! zbG{d~w{M_Qdjf0mZ=8k;NS7wE8MS}|sPjMOl79-;q_XT4I1yLk%cwWmdDOszCd5`e z6{$Z{jXLi^ZOJYih@YUg>@ezv-=K={4yq^zk$){@B!8&3vp+Z##w4G|2e5;;+%|@++&k#5W8$hRrm`^AR%|sPZL#SzMl$GT=pmh^2qEOF&8=bMl z^TbTTO}t3xt+tuqF^;BO`%FDW_dN;KlzO$&7}3W}-RFpngojYt)$}sc)ATG6B%&>R zJZVVAGjz8Qs=OBnFR>_mBq=SVl6cTI&}nxLRI6)=YNCbEKR2~nVg|98SWk2j9fX?7 zU=tCq1KK)5wcJVQp;1d9<`R^vT2u5z7VF2}9jZmuRUT1E*u)&7kr+v+Jsl(Z&I&mF zY}6}dDdBU@&2tyEwzfLR1`;!XI)5~WLu3buFht=-S!47caW(x zws`DL%WYeJU(jC=@B}(-|LG5y-YeGc;j?2F$IS`X=I2K0#-53ftj;|jx1`#o`<^-I zv%I#|VFv@&nNRtV$K&LO* K;=|+fFZ>UtPEgeV diff --git a/pod/locale/fr/LC_MESSAGES/djangojs.po b/pod/locale/fr/LC_MESSAGES/djangojs.po index 779b84ae55..dbc4acd496 100644 --- a/pod/locale/fr/LC_MESSAGES/djangojs.po +++ b/pod/locale/fr/LC_MESSAGES/djangojs.po @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: Esup-Pod\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-11-26 10:16+0100\n" -"PO-Revision-Date: 2021-11-26 10:18+0100\n" +"POT-Creation-Date: 2022-03-25 17:10+0100\n" +"PO-Revision-Date: 2021-03-26 17:58+0100\n" "Last-Translator: obado \n" "Language-Team: \n" "Language: fr\n" @@ -20,19 +20,24 @@ msgstr "" #: pod/enrichment/static/js/enrichment.js:9 #: pod/enrichment/static/js/enrichment.js:14 #: pod/enrichment/static/js/enrichment.js:258 -#: pod/enrichment/static/js/enrichment.js:263 +#: pod/enrichment/static/js/enrichment.js:263 pod/static/js/chapters.js:9 +#: pod/static/js/chapters.js:14 pod/static/js/enrichment.js:9 +#: pod/static/js/enrichment.js:14 pod/static/js/enrichment.js:258 +#: pod/static/js/enrichment.js:263 msgid "Get time from the player" msgstr "Obtenir le temps du lecteur" #: pod/chapter/static/js/chapters.js:34 #: pod/completion/static/js/completion.js:52 -#: pod/enrichment/static/js/enrichment.js:35 +#: pod/enrichment/static/js/enrichment.js:35 pod/static/js/chapters.js:34 +#: pod/static/js/completion.js:52 pod/static/js/enrichment.js:35 msgid "Error getting form." msgstr "Erreur lors de la récupération du formulaire." #: pod/chapter/static/js/chapters.js:38 #: pod/completion/static/js/completion.js:56 -#: pod/enrichment/static/js/enrichment.js:39 +#: pod/enrichment/static/js/enrichment.js:39 pod/static/js/chapters.js:38 +#: pod/static/js/completion.js:56 pod/static/js/enrichment.js:39 msgid "The form could not be recovered." msgstr "Le formulaire ne peut pas être récupéré." @@ -47,44 +52,60 @@ msgstr "Le formulaire ne peut pas être récupéré." #: pod/enrichment/static/js/enrichment.js:111 #: pod/enrichment/static/js/enrichment.js:141 #: pod/enrichment/static/js/enrichment.js:174 -#: pod/enrichment/static/js/enrichment.js:215 pod/main/static/js/main.js:468 -#: pod/main/static/js/main.js:478 pod/main/static/js/main.js:492 -#: pod/main/static/js/main.js:512 +#: pod/enrichment/static/js/enrichment.js:215 pod/main/static/js/main.js:479 +#: pod/main/static/js/main.js:489 pod/main/static/js/main.js:503 +#: pod/main/static/js/main.js:523 #: pod/podfile/static/podfile/js/filewidget.js:365 -#: pod/podfile/static/podfile/js/filewidget.js:398 +#: pod/podfile/static/podfile/js/filewidget.js:398 pod/static/js/chapters.js:82 +#: pod/static/js/chapters.js:111 pod/static/js/chapters.js:141 +#: pod/static/js/chapters.js:175 pod/static/js/chapters.js:221 +#: pod/static/js/completion.js:107 pod/static/js/completion.js:149 +#: pod/static/js/completion.js:211 pod/static/js/completion.js:247 +#: pod/static/js/enrichment.js:82 pod/static/js/enrichment.js:111 +#: pod/static/js/enrichment.js:141 pod/static/js/enrichment.js:174 +#: pod/static/js/enrichment.js:215 pod/static/js/main.js:468 +#: pod/static/js/main.js:478 pod/static/js/main.js:492 +#: pod/static/js/main.js:512 pod/static/podfile/js/filewidget.js:365 +#: pod/static/podfile/js/filewidget.js:398 msgid "You are no longer authenticated. Please log in again." msgstr "Vous n'êtes plus authentifié. Veuillez vous reconnectez." #: pod/chapter/static/js/chapters.js:199 -#: pod/enrichment/static/js/enrichment.js:193 pod/main/static/js/main.js:415 -#: pod/main/static/js/main.js:501 +#: pod/enrichment/static/js/enrichment.js:193 pod/main/static/js/main.js:426 +#: pod/main/static/js/main.js:512 pod/static/js/chapters.js:199 +#: pod/static/js/enrichment.js:193 pod/static/js/main.js:415 +#: pod/static/js/main.js:501 msgid "One or more errors have been found in the form." msgstr "Une ou plusieurs erreurs ont été trouvées dans le formulaire." #: pod/chapter/static/js/chapters.js:251 -#: pod/enrichment/static/js/enrichment.js:318 +#: pod/enrichment/static/js/enrichment.js:318 pod/static/js/chapters.js:251 +#: pod/static/js/enrichment.js:318 msgid "Please enter a title from 2 to 100 characters." msgstr "Veuillez entrer un titre contenant entre 2 et 100 caractères." -#: pod/chapter/static/js/chapters.js:266 +#: pod/chapter/static/js/chapters.js:266 pod/static/js/chapters.js:266 msgid "Please enter a correct start field between 0 and" msgstr "Veuillez entrer un correct temps de début compris entre 0 et" -#: pod/chapter/static/js/chapters.js:288 +#: pod/chapter/static/js/chapters.js:288 pod/static/js/chapters.js:288 msgid "The chapter" msgstr "Le chapitre" -#: pod/chapter/static/js/chapters.js:292 +#: pod/chapter/static/js/chapters.js:292 pod/static/js/chapters.js:292 msgid "starts at the same time." msgstr "commencent au même moment." #: pod/chapter/static/js/videojs-chapters.js:22 #: pod/chapter/static/js/videojs-chapters.js:24 #: pod/chapter/static/js/videojs-chapters.js:26 +#: pod/static/js/videojs-chapters.js:22 pod/static/js/videojs-chapters.js:24 +#: pod/static/js/videojs-chapters.js:26 msgid "Chapters" msgstr "Chapitres" #: pod/completion/static/js/caption_maker.js:29 +#: pod/static/js/caption_maker.js:29 msgid "" "WEBVTT\n" "\n" @@ -98,376 +119,463 @@ msgstr "" #: pod/completion/static/js/caption_maker.js:48 #: pod/completion/static/js/caption_maker.js:902 +#: pod/static/js/caption_maker.js:48 pod/static/js/caption_maker.js:902 msgid "Unrecognized caption file format." msgstr "Format de fichier de traduction non reconnu." #: pod/completion/static/js/caption_maker.js:59 +#: pod/static/js/caption_maker.js:59 msgid "There is no captions to save" msgstr "Il n’y a aucun sous-titre à sauvegarder" #: pod/completion/static/js/caption_maker.js:94 #: pod/completion/static/js/caption_maker.js:916 +#: pod/static/js/caption_maker.js:94 pod/static/js/caption_maker.js:916 msgid "Not a valid time track file." msgstr "Fichier de traduction invalide." #: pod/completion/static/js/caption_maker.js:115 +#: pod/static/js/caption_maker.js:115 msgid "error during exchange" msgstr "erreur durant le téléversement" #: pod/completion/static/js/caption_maker.js:119 +#: pod/static/js/caption_maker.js:119 msgid "no data could be stored." msgstr "aucune donnée ne peut être stockée." #: pod/completion/static/js/caption_maker.js:181 +#: pod/static/js/caption_maker.js:181 msgid "Are you sure you want to delete all caption?" msgstr "Êtes-vous sûr(e) de vouloir supprimer les sous-titre ?" #: pod/completion/static/js/caption_maker.js:867 +#: pod/static/js/caption_maker.js:867 msgid "Error reading caption file. Code = " msgstr "Erreur durant la lecture du fichier de traduction, Code = " #: pod/completion/static/js/caption_maker.js:874 +#: pod/static/js/caption_maker.js:874 msgid "Exception thrown reading caption file. Code = " msgstr "Une exception a été levée durant la lecture du fichier. Code = " #: pod/completion/static/js/caption_maker.js:878 +#: pod/static/js/caption_maker.js:878 msgid "Your browser does not support FileReader." msgstr "Votre navigateur ne supporte pas FileReader." #: pod/completion/static/js/caption_maker.js:885 +#: pod/static/js/caption_maker.js:885 +#, fuzzy +#| msgid "Error during exchange" msgid "Error loading caption file: " msgstr "Erreur durant le chargement du fichier de sous-titres : " #: pod/completion/static/js/completion.js:175 #: pod/podfile/static/podfile/js/filewidget.js:277 +#: pod/static/js/completion.js:175 pod/static/podfile/js/filewidget.js:277 msgid "Are you sure you want to delete this file?" msgstr "Êtes-vous sûr(e) de vouloir supprimer ce fichier ?" -#: pod/completion/static/js/completion.js:179 +#: pod/completion/static/js/completion.js:179 pod/static/js/completion.js:179 msgid "Are you sure you want to delete this contributor?" msgstr "Êtes-vous sûr(e) de vouloir supprimer ce contributeur ?" -#: pod/completion/static/js/completion.js:183 +#: pod/completion/static/js/completion.js:183 pod/static/js/completion.js:183 msgid "Are you sure you want to delete this document?" msgstr "Êtes-vous sûr(e) de vouloir supprimer ce document ?" -#: pod/completion/static/js/completion.js:187 +#: pod/completion/static/js/completion.js:187 pod/static/js/completion.js:187 msgid "Are you sure you want to delete this overlay?" msgstr "Êtes-vous sûr(e) de vouloir supprimer cette superposition ?" -#: pod/completion/static/js/completion.js:243 pod/main/static/js/main.js:419 +#: pod/completion/static/js/completion.js:243 pod/main/static/js/main.js:430 +#: pod/static/js/completion.js:243 pod/static/js/main.js:419 msgid "Changes have been saved." msgstr "Les modifications ont été sauvegardées." -#: pod/completion/static/js/completion.js:276 +#: pod/completion/static/js/completion.js:276 pod/static/js/completion.js:276 msgid "Display" msgstr "Afficher" -#: pod/completion/static/js/completion.js:276 +#: pod/completion/static/js/completion.js:276 pod/static/js/completion.js:276 msgid "section" msgstr "section" -#: pod/completion/static/js/completion.js:304 +#: pod/completion/static/js/completion.js:304 pod/static/js/completion.js:304 msgid "Please enter a name from 2 to 100 caracteres." msgstr "Veuillez entrer un nom entre 2 et 200 caractères." -#: pod/completion/static/js/completion.js:316 +#: pod/completion/static/js/completion.js:316 pod/static/js/completion.js:316 msgid "You cannot enter a weblink with more than 200 caracteres." msgstr "Vous ne pouvez pas entrer un lien web avec plus de 200 caractères." -#: pod/completion/static/js/completion.js:328 +#: pod/completion/static/js/completion.js:328 pod/static/js/completion.js:328 msgid "Please enter a role." msgstr "Veuillez entrer un rôle." -#: pod/completion/static/js/completion.js:345 +#: pod/completion/static/js/completion.js:345 pod/static/js/completion.js:345 msgid "" "There is already a contributor with this same name and role in the list." msgstr "" "Il existe déjà un contributeur avec le même nom et le même rôle dans cette " "liste." -#: pod/completion/static/js/completion.js:361 +#: pod/completion/static/js/completion.js:361 pod/static/js/completion.js:361 msgid "Please enter a correct kind." msgstr "Veuillez entrer un genre correct." -#: pod/completion/static/js/completion.js:376 +#: pod/completion/static/js/completion.js:376 pod/static/js/completion.js:376 msgid "Please select a language." msgstr "Veuillez renseigner une langue." -#: pod/completion/static/js/completion.js:391 +#: pod/completion/static/js/completion.js:391 pod/static/js/completion.js:391 msgid "Please specify a track file." msgstr "Veuillez joindre un fichier de piste vidéo." -#: pod/completion/static/js/completion.js:401 +#: pod/completion/static/js/completion.js:401 pod/static/js/completion.js:401 msgid "Only .vtt format is allowed." msgstr "Seulement le format \".vtt\" est autorisé." -#: pod/completion/static/js/completion.js:436 +#: pod/completion/static/js/completion.js:436 pod/static/js/completion.js:436 msgid "There is already a track with the same kind and language in the list." msgstr "" "Il existe déjà une piste vidéo avec le même genre et la même langue dans la " "liste." #: pod/completion/static/js/completion.js:452 -#: pod/enrichment/static/js/enrichment.js:422 +#: pod/enrichment/static/js/enrichment.js:422 pod/static/js/completion.js:452 +#: pod/static/js/enrichment.js:422 msgid "Please select a document." msgstr "Veuillez joindre un document." -#: pod/completion/static/js/completion.js:465 +#: pod/completion/static/js/completion.js:465 pod/static/js/completion.js:465 msgid "Iframe and Script tags are not allowed." msgstr "Les balises Iframe et Script ne sont pas autorisées." -#: pod/enrichment/static/js/enrichment.js:333 +#: pod/enrichment/static/js/enrichment.js:333 pod/static/js/enrichment.js:333 msgid "Please enter a correct start from 0 to " msgstr "Veuillez entrer un correct temps de début compris entre 0 et " -#: pod/enrichment/static/js/enrichment.js:349 +#: pod/enrichment/static/js/enrichment.js:349 pod/static/js/enrichment.js:349 msgid "Please enter a correct end from 1 to " msgstr "Veuillez entrer un correct temps de fin compris entre 1 et " -#: pod/enrichment/static/js/enrichment.js:367 +#: pod/enrichment/static/js/enrichment.js:367 pod/static/js/enrichment.js:367 msgid "Please enter a correct image." msgstr "Veuillez joindre une image correct." -#: pod/enrichment/static/js/enrichment.js:380 +#: pod/enrichment/static/js/enrichment.js:380 pod/static/js/enrichment.js:380 msgid "Please enter a correct richtext." msgstr "Veuillez entrer un texte riche correct." -#: pod/enrichment/static/js/enrichment.js:393 +#: pod/enrichment/static/js/enrichment.js:393 pod/static/js/enrichment.js:393 msgid "Please enter a correct weblink." msgstr "Veuillez entrer un lien web correct." -#: pod/enrichment/static/js/enrichment.js:404 +#: pod/enrichment/static/js/enrichment.js:404 pod/static/js/enrichment.js:404 msgid "Weblink must be less than 200 characters." msgstr "Un lien web doit contenir moins de 200 caractères." -#: pod/enrichment/static/js/enrichment.js:435 +#: pod/enrichment/static/js/enrichment.js:435 pod/static/js/enrichment.js:435 msgid "Please enter a correct embed." msgstr "Veuillez entrer une intégration correct." -#: pod/enrichment/static/js/enrichment.js:446 +#: pod/enrichment/static/js/enrichment.js:446 pod/static/js/enrichment.js:446 msgid "Embed field must be less than 300 characters." msgstr "Un lien d'intégration doit être inférieur à 300 caractères." -#: pod/enrichment/static/js/enrichment.js:459 +#: pod/enrichment/static/js/enrichment.js:459 pod/static/js/enrichment.js:459 msgid "Please enter a type in index field." msgstr "Veuillez entrer un type." -#: pod/enrichment/static/js/enrichment.js:474 +#: pod/enrichment/static/js/enrichment.js:474 pod/static/js/enrichment.js:474 msgid "The start field value is greater than the end field one." msgstr "Le temps de début est supérieur au temps de fin." -#: pod/enrichment/static/js/enrichment.js:476 +#: pod/enrichment/static/js/enrichment.js:476 pod/static/js/enrichment.js:476 msgid "The end field value is greater than the video duration." msgstr "La temps de fin est supérieur à la durée de la vidéo." -#: pod/enrichment/static/js/enrichment.js:478 +#: pod/enrichment/static/js/enrichment.js:478 pod/static/js/enrichment.js:478 msgid "End field and start field cannot be equal." msgstr "Le commencement et la fin ne peuvent être équivalents." -#: pod/enrichment/static/js/enrichment.js:503 +#: pod/enrichment/static/js/enrichment.js:503 pod/static/js/enrichment.js:503 msgid "There is an overlap with the enrichment " msgstr "Il y a un chevauchement avec l'enrichissement " -#: pod/enrichment/static/js/enrichment.js:507 +#: pod/enrichment/static/js/enrichment.js:507 pod/static/js/enrichment.js:507 msgid "please change start and/or end values." msgstr "veuillez changer de valeur de début et/ou de fin." +#: pod/live/static/js/broadcaster_from_building.js:12 +#: pod/static/js/broadcaster_from_building.js:12 +msgid "Restricted because the broadcaster is restricted" +msgstr "Obligatoirement restreint car le diffuseur est restreint" + +#: pod/live/static/js/broadcaster_from_building.js:16 +#: pod/static/js/broadcaster_from_building.js:16 +msgid "" +"If this box is checked, the event will only be accessible to authenticated " +"users." +msgstr "" +"Si cette case est cochée l'évènement ne sera accessible qu'aux utilisateurs " +"authentifiés" + +#: pod/live/static/js/broadcaster_from_building.js:39 +#: pod/static/js/broadcaster_from_building.js:39 +msgid "an error occurred on broadcaster fetch ..." +msgstr "Une erreur s'est produite lors du chargement des diffuseurs" + +#: pod/live/static/js/broadcaster_from_building.js:62 +#: pod/static/js/broadcaster_from_building.js:62 +msgid "No broadcaster set for this building" +msgstr "Pas de diffuseur pour ce bâtiment" + +#: pod/live/static/js/broadcaster_from_building.js:77 +#: pod/static/js/broadcaster_from_building.js:77 +msgid "an error occurred during broadcasters load ..." +msgstr "Une erreur s'est produite lors du chargement des diffuseurs" + #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gView" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gBox" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "overlay" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "loading" msgstr "chargement" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "states.hover" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "states.disabled" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "pager.pagerSelect" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "pager.pagerInput" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "pager.pagerButton" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "states.hoverTh" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "colHeaders" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "hTable" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "states.select" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "resizer" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gridFooter" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "rowFooter" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gridRow" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "grid" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "top" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "bottom" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "hDiv" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "pager.pager" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "titleButton" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gridTitle" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "toolbarUpper" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "toolbarBottom" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "rowNum" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gridError" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gridErrorText" msgstr "" -#: pod/main/static/js/main.js:378 +#: pod/main/static/js/main.js:389 pod/static/js/main.js:378 msgid "Are you sure you want to delete this element?" msgstr "Êtes-vous sûr(e) de vouloir supprimer cet élément ?" -#: pod/main/static/js/main.js:454 +#: pod/main/static/js/main.js:465 #: pod/podfile/static/podfile/js/filewidget.js:114 -#: pod/podfile/static/podfile/js/filewidget.js:391 +#: pod/podfile/static/podfile/js/filewidget.js:391 pod/static/js/main.js:454 +#: pod/static/podfile/js/filewidget.js:114 +#: pod/static/podfile/js/filewidget.js:391 msgid "Error during exchange" msgstr "Erreur durant le téléversement" -#: pod/main/static/js/main.js:458 -#: pod/podfile/static/podfile/js/filewidget.js:114 +#: pod/main/static/js/main.js:469 +#: pod/podfile/static/podfile/js/filewidget.js:114 pod/static/js/main.js:458 +#: pod/static/podfile/js/filewidget.js:114 msgid "No data could be stored." msgstr "Aucune donnée ne peut être stockée." -#: pod/main/static/js/main.js:531 +#: pod/main/static/js/main.js:542 pod/static/js/main.js:531 msgid "Change your picture" msgstr "Changer votre image" -#: pod/main/static/js/main.js:538 +#: pod/main/static/js/main.js:549 pod/static/js/main.js:538 msgid "Add your picture" msgstr "Ajouter votre image" -#: pod/main/static/js/main.js:666 +#: pod/main/static/js/main.js:677 pod/static/js/main.js:666 msgid "text copied" msgstr "texte copié" -#: pod/main/static/js/main.js:718 +#: pod/main/static/js/main.js:729 pod/static/js/main.js:718 msgid "Errors appear in the form, please correct them" msgstr "Des erreurs sont présentes dans le formulaire, veuillez les corriger" -#: pod/main/static/js/main.js:750 +#: pod/main/static/js/main.js:761 pod/static/js/main.js:750 msgid "The file size exceeds the maximum allowed value:" msgstr "Le fichier dépasse la taille maximum autorisée :" -#: pod/main/static/js/main.js:753 +#: pod/main/static/js/main.js:764 pod/static/js/main.js:753 msgid " GB." msgstr " Go." -#: pod/main/static/js/main.js:771 +#: pod/main/static/js/main.js:782 pod/static/js/main.js:771 msgid "The file extension not in the allowed extension:" msgstr "" "Cette extension de fichier n'est pas présente dans les extensions " "autorisées :" -#: pod/playlist/static/js/playlist.js:172 +#: pod/playlist/static/js/playlist.js:172 pod/static/js/playlist.js:172 msgid "The video can not be added from this page." -msgstr "La vidéo ne peut être ajoutée depuis cette page." +msgstr "" -#: pod/playlist/static/js/playlist.js:179 +#: pod/playlist/static/js/playlist.js:178 pod/static/js/playlist.js:178 +#, fuzzy +#| msgid "videos found" msgid "The video slug not found." -msgstr "La vidéo est introuvable." +msgstr "vidéos trouvées" #: pod/podfile/static/podfile/js/filewidget.js:11 +#: pod/static/podfile/js/filewidget.js:11 msgid "Change image" msgstr "Changer d'image" #: pod/podfile/static/podfile/js/filewidget.js:13 +#: pod/static/podfile/js/filewidget.js:13 msgid "Change file" msgstr "Changer de fichier" #: pod/podfile/static/podfile/js/filewidget.js:28 +#: pod/static/podfile/js/filewidget.js:28 msgid "Open file in a new tab" msgstr "Ouvrir le fichier dans un nouvel onglet" #: pod/podfile/static/podfile/js/filewidget.js:55 +#: pod/static/podfile/js/filewidget.js:55 msgid "This folder is empty" msgstr "Ce dossier est vide" #: pod/podfile/static/podfile/js/filewidget.js:134 #: pod/podfile/static/podfile/js/filewidget.js:141 +#: pod/static/podfile/js/filewidget.js:134 +#: pod/static/podfile/js/filewidget.js:141 msgid "Change" msgstr "Changer" #: pod/podfile/static/podfile/js/filewidget.js:149 +#: pod/static/podfile/js/filewidget.js:149 msgid "Enter new name of folder" msgstr "Indiquer un nouveau nom au dossier" #: pod/podfile/static/podfile/js/filewidget.js:167 +#: pod/static/admin/js/SelectFilter2.js:86 +#: pod/static/podfile/js/filewidget.js:167 msgid "Remove" msgstr "Retirer" @@ -475,50 +583,300 @@ msgstr "Retirer" #: pod/podfile/static/podfile/js/filewidget.js:202 #: pod/podfile/static/podfile/js/filewidget.js:235 #: pod/podfile/static/podfile/js/filewidget.js:254 +#: pod/static/podfile/js/filewidget.js:180 +#: pod/static/podfile/js/filewidget.js:202 +#: pod/static/podfile/js/filewidget.js:235 +#: pod/static/podfile/js/filewidget.js:254 msgid "Server error" msgstr "Erreur du serveur" #: pod/podfile/static/podfile/js/filewidget.js:188 +#: pod/static/podfile/js/filewidget.js:188 msgid "Add" msgstr "Ajouter" #: pod/podfile/static/podfile/js/filewidget.js:268 +#: pod/static/podfile/js/filewidget.js:268 msgid "Are you sure you want to delete this folder?" msgstr "Êtes-vous sûr(e) de vouloir supprimer ce dossier ?" #: pod/podfile/static/podfile/js/filewidget.js:477 +#: pod/static/podfile/js/filewidget.js:477 msgid "See more" msgstr "Voir plus" +#: pod/static/admin/js/SelectFilter2.js:47 +#, javascript-format +msgid "Available %s" +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:53 +#, javascript-format +msgid "" +"This is the list of available %s. You may choose some by selecting them in " +"the box below and then clicking the \"Choose\" arrow between the two boxes." +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:69 +#, javascript-format +msgid "Type into this box to filter down the list of available %s." +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:74 +msgid "Filter" +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:78 +msgid "Choose all" +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:78 +#, javascript-format +msgid "Click to choose all %s at once." +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:84 +msgid "Choose" +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:92 +#, javascript-format +msgid "Chosen %s" +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:98 +#, javascript-format +msgid "" +"This is the list of chosen %s. You may remove some by selecting them in the " +"box below and then clicking the \"Remove\" arrow between the two boxes." +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:108 +#, fuzzy +#| msgid "Remove" +msgid "Remove all" +msgstr "Retirer" + +#: pod/static/admin/js/SelectFilter2.js:108 +#, javascript-format +msgid "Click to remove all chosen %s at once." +msgstr "" + +#: pod/static/admin/js/actions.js:48 pod/static/admin/js/actions.min.js:2 +msgid "%(sel)s of %(cnt)s selected" +msgid_plural "%(sel)s of %(cnt)s selected" +msgstr[0] "" +msgstr[1] "" + +#: pod/static/admin/js/actions.js:117 pod/static/admin/js/actions.min.js:4 +msgid "" +"You have unsaved changes on individual editable fields. If you run an " +"action, your unsaved changes will be lost." +msgstr "" + +#: pod/static/admin/js/actions.js:129 pod/static/admin/js/actions.min.js:5 +msgid "" +"You have selected an action, but you haven't saved your changes to " +"individual fields yet. Please click OK to save. You'll need to re-run the " +"action." +msgstr "" + +#: pod/static/admin/js/actions.js:131 pod/static/admin/js/actions.min.js:5 +msgid "" +"You have selected an action, and you haven't made any changes on individual " +"fields. You're probably looking for the Go button rather than the Save " +"button." +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:74 +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +msgstr[0] "" +msgstr[1] "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:82 +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +msgstr[0] "" +msgstr[1] "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:109 +#: pod/static/admin/js/admin/DateTimeShortcuts.js:160 +msgid "Now" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:127 +msgid "Choose a Time" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:157 +msgid "Choose a time" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:165 +msgid "Midnight" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:170 +msgid "6 a.m." +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:175 +msgid "Noon" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:180 +msgid "6 p.m." +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:188 +#: pod/static/admin/js/admin/DateTimeShortcuts.js:348 +#: pod/static/js/comment-script.js:98 pod/video/static/js/comment-script.js:98 +msgid "Cancel" +msgstr "Annuler" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:253 +#: pod/static/admin/js/admin/DateTimeShortcuts.js:333 +msgid "Today" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:270 +msgid "Choose a Date" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:327 +msgid "Yesterday" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:339 +msgid "Tomorrow" +msgstr "" + +#: pod/static/admin/js/calendar.js:12 +msgid "January" +msgstr "" + +#: pod/static/admin/js/calendar.js:13 +msgid "February" +msgstr "" + +#: pod/static/admin/js/calendar.js:14 +msgid "March" +msgstr "" + +#: pod/static/admin/js/calendar.js:15 +msgid "April" +msgstr "" + +#: pod/static/admin/js/calendar.js:16 +msgid "May" +msgstr "" + +#: pod/static/admin/js/calendar.js:17 +msgid "June" +msgstr "" + +#: pod/static/admin/js/calendar.js:18 +msgid "July" +msgstr "" + +#: pod/static/admin/js/calendar.js:19 +msgid "August" +msgstr "" + +#: pod/static/admin/js/calendar.js:20 +msgid "September" +msgstr "" + +#: pod/static/admin/js/calendar.js:21 +msgid "October" +msgstr "" + +#: pod/static/admin/js/calendar.js:22 +msgid "November" +msgstr "" + +#: pod/static/admin/js/calendar.js:23 +msgid "December" +msgstr "" + +#: pod/static/admin/js/calendar.js:26 +msgctxt "one letter Sunday" +msgid "S" +msgstr "" + +#: pod/static/admin/js/calendar.js:27 +msgctxt "one letter Monday" +msgid "M" +msgstr "" + +#: pod/static/admin/js/calendar.js:28 +msgctxt "one letter Tuesday" +msgid "T" +msgstr "" + +#: pod/static/admin/js/calendar.js:29 +msgctxt "one letter Wednesday" +msgid "W" +msgstr "" + +#: pod/static/admin/js/calendar.js:30 +msgctxt "one letter Thursday" +msgid "T" +msgstr "" + +#: pod/static/admin/js/calendar.js:31 +msgctxt "one letter Friday" +msgid "F" +msgstr "" + +#: pod/static/admin/js/calendar.js:32 +msgctxt "one letter Saturday" +msgid "S" +msgstr "" + +#: pod/static/admin/js/collapse.js:10 pod/static/admin/js/collapse.js:21 +#: pod/static/admin/js/collapse.min.js:1 +msgid "Show" +msgstr "" + +#: pod/static/admin/js/collapse.js:18 pod/static/admin/js/collapse.min.js:1 +msgid "Hide" +msgstr "" + +#: pod/static/js/change_video_owner.js:104 #: pod/video/static/js/change_video_owner.js:104 msgid "No element found" msgstr "Aucun élément trouvé" +#: pod/static/js/change_video_owner.js:637 #: pod/video/static/js/change_video_owner.js:637 msgid "An error occurred during the change of owner" msgstr "Une erreur s'est produite lors du changement de propriétaire" +#: pod/static/js/change_video_owner.js:648 #: pod/video/static/js/change_video_owner.js:648 msgid "Please complete all fields correctly" msgstr "Veuillez remplir tous les champs correctement" -#: pod/video/static/js/comment-script.js:29 +#: pod/static/js/comment-script.js:29 pod/video/static/js/comment-script.js:29 msgid "Answer" msgstr "Réponse" -#: pod/video/static/js/comment-script.js:30 +#: pod/static/js/comment-script.js:30 pod/video/static/js/comment-script.js:30 msgid "Answers" msgstr "Réponses" +#: pod/static/js/comment-script.js:85 pod/static/js/comment-script.js:239 #: pod/video/static/js/comment-script.js:85 #: pod/video/static/js/comment-script.js:239 msgid "Delete" msgstr "Supprimer" -#: pod/video/static/js/comment-script.js:98 -msgid "Cancel" -msgstr "Annuler" - +#: pod/static/js/comment-script.js:177 pod/static/js/comment-script.js:447 +#: pod/static/js/comment-script.js:464 #: pod/video/static/js/comment-script.js:177 #: pod/video/static/js/comment-script.js:447 #: pod/video/static/js/comment-script.js:464 @@ -528,170 +886,219 @@ msgid_plural "%s votes" msgstr[0] "%s vote" msgstr[1] "%s votes" +#: pod/static/js/comment-script.js:188 #: pod/video/static/js/comment-script.js:188 msgid "Agree with the comment" msgstr "D’accord avec ce commentaire" +#: pod/static/js/comment-script.js:215 #: pod/video/static/js/comment-script.js:215 msgid "Reply to comment" msgstr "Répondre au commentaire" +#: pod/static/js/comment-script.js:218 #: pod/video/static/js/comment-script.js:218 msgid "Reply" msgstr "Répondre" +#: pod/static/js/comment-script.js:236 #: pod/video/static/js/comment-script.js:236 msgid "Remove this comment" msgstr "Supprimer ce commentaire" +#: pod/static/js/comment-script.js:259 #: pod/video/static/js/comment-script.js:259 msgid "Add a public comment" msgstr "Ajouter un commentaire public" +#: pod/static/js/comment-script.js:383 #: pod/video/static/js/comment-script.js:383 msgid "Show answers" msgstr "Afficher les réponses" +#: pod/static/js/comment-script.js:602 #: pod/video/static/js/comment-script.js:602 msgid "Comment has been deleted successfully." msgstr "Commentaire a été supprimé avec succès." +#: pod/static/js/filter_aside_video_list_refresh.js:58 +#: pod/video/static/js/filter_aside_video_list_refresh.js:60 +#, fuzzy +#| msgid "An error occurred during the change of owner" +msgid "An Error occurred while processing " +msgstr "Une erreur s'est produite lors du changement de propriétaire" + +#: pod/static/js/regroup_videos_by_theme.js:51 +#: pod/static/js/video_category.js:530 #: pod/video/static/js/regroup_videos_by_theme.js:51 #: pod/video/static/js/video_category.js:530 msgid "This content is password protected." msgstr "Ce contenu est protégé par mot de passe." +#: pod/static/js/regroup_videos_by_theme.js:59 +#: pod/static/js/video_category.js:538 #: pod/video/static/js/regroup_videos_by_theme.js:59 #: pod/video/static/js/video_category.js:538 msgid "This content is chaptered." msgstr "Ce contenu est chapitré." +#: pod/static/js/regroup_videos_by_theme.js:67 +#: pod/static/js/video_category.js:546 #: pod/video/static/js/regroup_videos_by_theme.js:67 #: pod/video/static/js/video_category.js:546 msgid "This content is in draft." msgstr "Ce contenu est en brouillon." +#: pod/static/js/regroup_videos_by_theme.js:76 +#: pod/static/js/video_category.js:436 pod/static/js/video_category.js:555 #: pod/video/static/js/regroup_videos_by_theme.js:76 #: pod/video/static/js/video_category.js:436 #: pod/video/static/js/video_category.js:555 msgid "Video content." msgstr "Contenu vidéo." +#: pod/static/js/regroup_videos_by_theme.js:81 +#: pod/static/js/video_category.js:437 pod/static/js/video_category.js:560 #: pod/video/static/js/regroup_videos_by_theme.js:81 #: pod/video/static/js/video_category.js:437 #: pod/video/static/js/video_category.js:560 msgid "Audio content." msgstr "Contenu audio." +#: pod/static/js/regroup_videos_by_theme.js:86 +#: pod/static/js/video_category.js:565 #: pod/video/static/js/regroup_videos_by_theme.js:86 #: pod/video/static/js/video_category.js:565 msgid "Edit the video" msgstr "Éditer la vidéo" +#: pod/static/js/regroup_videos_by_theme.js:87 +#: pod/static/js/video_category.js:566 #: pod/video/static/js/regroup_videos_by_theme.js:87 #: pod/video/static/js/video_category.js:566 msgid "Complete the video" msgstr "Compléter la vidéo" +#: pod/static/js/regroup_videos_by_theme.js:88 +#: pod/static/js/video_category.js:567 #: pod/video/static/js/regroup_videos_by_theme.js:88 #: pod/video/static/js/video_category.js:567 msgid "Chapter the video" msgstr "Chapitrer la vidéo" +#: pod/static/js/regroup_videos_by_theme.js:89 +#: pod/static/js/video_category.js:568 #: pod/video/static/js/regroup_videos_by_theme.js:89 #: pod/video/static/js/video_category.js:568 msgid "Delete the video" msgstr "Supprimer la vidéo" +#: pod/static/js/regroup_videos_by_theme.js:270 #: pod/video/static/js/regroup_videos_by_theme.js:270 msgid "Loading videos.." msgstr "Chargement videos.." +#: pod/static/js/validate-date_delete-field.js:32 #: pod/video/static/js/validate-date_delete-field.js:32 msgid "The date must be before or equal to" msgstr "La date doit être anterieure ou égale à" -#: pod/video/static/js/video_category.js:27 +#: pod/static/js/video_category.js:27 pod/video/static/js/video_category.js:27 msgid "Category changes saved successfully" msgstr "Les changements sur la catégorie ont été sauvegardés avec succès" -#: pod/video/static/js/video_category.js:29 +#: pod/static/js/video_category.js:29 pod/video/static/js/video_category.js:29 msgid "You cannot add two categories with the same title." msgstr "Vous ne pouvez pas ajouter deux catégories avec le même titre." -#: pod/video/static/js/video_category.js:31 +#: pod/static/js/video_category.js:31 pod/video/static/js/video_category.js:31 msgid "Category deleted successfully" msgstr "Catégorie supprimée avec succès" -#: pod/video/static/js/video_category.js:33 +#: pod/static/js/video_category.js:33 pod/video/static/js/video_category.js:33 msgid "An error occured, please refresh the page and try again." msgstr "" "Une erreur est survenue, veuillez recharger la page et réessayer à nouveau." -#: pod/video/static/js/video_category.js:35 +#: pod/static/js/video_category.js:35 pod/video/static/js/video_category.js:35 msgid "Category title field is required." msgstr "Le champ category titre est requis." -#: pod/video/static/js/video_category.js:36 +#: pod/static/js/video_category.js:36 pod/video/static/js/video_category.js:36 msgid "Save category" msgstr "Sauvegarder catégorie" +#: pod/static/js/video_category.js:187 #: pod/video/static/js/video_category.js:187 msgid "videos found" msgstr "vidéos trouvées" +#: pod/static/js/video_category.js:187 #: pod/video/static/js/video_category.js:187 msgid "video found" msgstr "vidéo trouvée" +#: pod/static/js/video_category.js:191 #: pod/video/static/js/video_category.js:191 msgid "Sorry, no video found" msgstr "Désolé, aucune vidéo trouvée" +#: pod/static/js/video_category.js:455 #: pod/video/static/js/video_category.js:455 msgid "Edit the category" msgstr "Éditer la catégorie" +#: pod/static/js/video_category.js:468 #: pod/video/static/js/video_category.js:468 msgid "Delete the category" msgstr "Supprimer la catégorie" +#: pod/static/js/video_category.js:636 #: pod/video/static/js/video_category.js:636 msgid "Success.." msgstr "Succès.." +#: pod/static/js/video_category.js:637 #: pod/video/static/js/video_category.js:637 msgid "Error.." msgstr "Erreur.." +#: pod/static/js/video_category.js:907 #: pod/video/static/js/video_category.js:907 msgid "Category created successfully" msgstr "Catégorie créée avec succès" +#: pod/static/js/video_category.js:927 #: pod/video/static/js/video_category.js:927 msgid "Create category" msgstr "Créer catégorie" +#: pod/static/js/video_stats_view.js:19 #: pod/video/static/js/video_stats_view.js:19 msgid "Title" msgstr "Titre" +#: pod/static/js/video_stats_view.js:20 #: pod/video/static/js/video_stats_view.js:20 msgid "View during the day" msgstr "Vue de la journée" +#: pod/static/js/video_stats_view.js:21 #: pod/video/static/js/video_stats_view.js:21 msgid "View during the month" msgstr "Vue du mois" +#: pod/static/js/video_stats_view.js:22 #: pod/video/static/js/video_stats_view.js:22 msgid "View during the year" msgstr "Vue de l'année" +#: pod/static/js/video_stats_view.js:23 #: pod/video/static/js/video_stats_view.js:23 msgid "Total view from creation" msgstr "Vue totale depuis création" +#: pod/static/js/video_stats_view.js:24 #: pod/video/static/js/video_stats_view.js:24 msgid "Slug" msgstr "Titre court" diff --git a/pod/locale/nl/LC_MESSAGES/django.po b/pod/locale/nl/LC_MESSAGES/django.po index 1952b397bd..8d1031e140 100644 --- a/pod/locale/nl/LC_MESSAGES/django.po +++ b/pod/locale/nl/LC_MESSAGES/django.po @@ -1,11 +1,11 @@ # Esup Pod main dutch strings. # This file is distributed under the same license as the POD package. -# +# msgid "" msgstr "" "Project-Id-Version: Pod\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-03-21 13:26+0000\n" +"POT-Creation-Date: 2022-06-14 06:32+0000\n" "PO-Revision-Date: 2021-11-25 16:53+0100\n" "Last-Translator: obado \n" "Language-Team: \n" @@ -20,15 +20,15 @@ msgstr "" msgid "Email" msgstr "E-mail" -#: pod/authentication/admin.py:130 pod/authentication/models.py:88 -#: pod/video/admin.py:177 +#: pod/authentication/admin.py:137 pod/authentication/models.py:88 +#: pod/video/admin.py:178 msgid "Establishment" msgstr "" #: pod/authentication/forms.py:57 pod/authentication/forms.py:58 #: pod/main/templates/navbar.html:32 #: pod/main/templates/navbar_collapse.html:127 pod/podfile/models.py:41 -#: pod/video/forms.py:371 pod/video/models.py:323 +#: pod/video/forms.py:371 pod/video/models.py:324 #: pod/video/templates/videos/filter_aside.html:14 msgid "Users" msgstr "" @@ -37,60 +37,60 @@ msgstr "" msgid "local" msgstr "" -#: pod/authentication/models.py:38 +#: pod/authentication/models.py:38 pod/custom/settings_local.py:1015 msgid "student" msgstr "student" -#: pod/authentication/models.py:39 +#: pod/authentication/models.py:39 pod/custom/settings_local.py:1016 msgid "faculty" msgstr "faculteit" -#: pod/authentication/models.py:40 +#: pod/authentication/models.py:40 pod/custom/settings_local.py:1017 msgid "staff" msgstr "personeel" -#: pod/authentication/models.py:41 +#: pod/authentication/models.py:41 pod/custom/settings_local.py:1018 msgid "employee" msgstr "werknemer" -#: pod/authentication/models.py:42 +#: pod/authentication/models.py:42 pod/custom/settings_local.py:1019 msgid "member" msgstr "lid" -#: pod/authentication/models.py:43 +#: pod/authentication/models.py:43 pod/custom/settings_local.py:1020 msgid "affiliate" msgstr "filiaal" -#: pod/authentication/models.py:44 +#: pod/authentication/models.py:44 pod/custom/settings_local.py:1021 msgid "alum" msgstr "" -#: pod/authentication/models.py:45 +#: pod/authentication/models.py:45 pod/custom/settings_local.py:1022 msgid "library-walk-in" msgstr "" -#: pod/authentication/models.py:46 +#: pod/authentication/models.py:46 pod/custom/settings_local.py:1023 msgid "researcher" msgstr "" -#: pod/authentication/models.py:47 +#: pod/authentication/models.py:47 pod/custom/settings_local.py:1024 msgid "retired" msgstr "" -#: pod/authentication/models.py:48 +#: pod/authentication/models.py:48 pod/custom/settings_local.py:1025 msgid "emeritus" msgstr "" -#: pod/authentication/models.py:49 +#: pod/authentication/models.py:49 pod/custom/settings_local.py:1026 msgid "teacher" msgstr "" -#: pod/authentication/models.py:50 +#: pod/authentication/models.py:50 pod/custom/settings_local.py:1027 msgid "registered-reader" msgstr "" #: pod/authentication/models.py:82 pod/recorder/models.py:307 -#: pod/video/models.py:1868 pod/video/models.py:1921 pod/video/views.py:1633 +#: pod/video/models.py:1883 pod/video/models.py:1936 pod/video/views.py:1633 msgid "Comment" msgstr "" @@ -113,7 +113,7 @@ msgstr "" #: pod/authentication/templates/registration/login.html:11 #: pod/authentication/templates/registration/login.html:14 #: pod/authentication/templates/registration/login.html:64 -#: pod/main/templates/navbar.html:133 +#: pod/main/templates/navbar.html:136 msgid "Log in" msgstr "Inloggen" @@ -148,9 +148,11 @@ msgstr "" #: pod/bbb/views.py:254 pod/completion/templates/video_caption_maker.html:98 #: pod/enrichment/templates/enrichment/group_enrichment.html:29 #: pod/interactive/templates/interactive/group_interactive.html:27 -#: pod/live/templates/live/live-form.html:10 -#: pod/main/templates/contact_us.html:17 pod/main/tests/test_views.py:99 -#: pod/main/views.py:300 pod/playlist/views.py:217 +#: pod/live/templates/live/event-form.html:10 +#: pod/live/templates/live/event_delete.html:39 pod/live/views.py:489 +#: pod/live/views.py:517 pod/main/templates/contact_us.html:17 +#: pod/main/tests/test_views.py:99 pod/main/views.py:300 +#: pod/playlist/views.py:217 #: pod/recorder/templates/recorder/add_recording.html:34 #: pod/recorder/templates/recorder/record_delete.html:31 #: pod/recorder/views.py:176 pod/recorder/views.py:376 @@ -175,7 +177,9 @@ msgstr "" #: pod/completion/templates/video_caption_maker.html:106 #: pod/enrichment/templates/enrichment/group_enrichment.html:51 #: pod/interactive/templates/interactive/group_interactive.html:49 -#: pod/live/templates/live/live-form.html:25 +#: pod/live/templates/live/event-form.html:25 +#: pod/live/templates/live/event_delete.html:65 +#: pod/live/templates/live/event_edit.html:84 #: pod/main/templates/contact_us.html:40 #: pod/playlist/templates/playlist/playlist_video_list.html:72 #: pod/recorder/templates/recorder/add_recording.html:57 @@ -193,7 +197,7 @@ msgid "Please provide a valid value for this field" msgstr "" #: pod/authentication/templates/userpicture/userpicture.html:54 -#: pod/bbb/templates/bbb/list_meeting.html:56 pod/main/templates/base.html:128 +#: pod/bbb/templates/bbb/list_meeting.html:56 pod/main/templates/base.html:134 #: pod/main/templates/navbar_collapse.html:43 #: pod/main/templates/navbar_collapse.html:91 #: pod/main/templates/navbar_collapse.html:117 @@ -214,7 +218,7 @@ msgstr "" msgid "Authentication" msgstr "" -#: pod/bbb/admin.py:47 pod/video/admin.py:227 +#: pod/bbb/admin.py:47 pod/video/admin.py:228 msgid "Encode selected" msgstr "" @@ -261,7 +265,7 @@ msgid "Waiting for encoding" msgstr "" #: pod/bbb/models.py:39 pod/bbb/templates/bbb/card.html:26 -#: pod/video/models.py:796 +#: pod/video/models.py:797 msgid "Encoding in progress" msgstr "" @@ -269,7 +273,7 @@ msgstr "" msgid "Already published" msgstr "" -#: pod/bbb/models.py:43 pod/video/models.py:871 pod/video/models.py:1771 +#: pod/bbb/models.py:43 pod/video/models.py:879 pod/video/models.py:1786 msgid "Encoding step" msgstr "" @@ -375,7 +379,7 @@ msgstr "" msgid "Start date" msgstr "" -#: pod/bbb/models.py:178 +#: pod/bbb/models.py:178 pod/live/models.py:342 msgid "Start date of the live." msgstr "" @@ -415,8 +419,8 @@ msgstr "" msgid "Username / User id, that want to perform the live." msgstr "" -#: pod/bbb/models.py:214 pod/live/models.py:127 pod/recorder/models.py:166 -#: pod/video/forms.py:282 pod/video/models.py:757 +#: pod/bbb/models.py:214 pod/live/models.py:157 pod/live/models.py:393 +#: pod/recorder/models.py:166 pod/video/forms.py:282 pod/video/models.py:758 msgid "Restricted access" msgstr "" @@ -424,7 +428,8 @@ msgstr "" msgid "Is live only accessible to authenticated users?" msgstr "" -#: pod/bbb/models.py:222 pod/live/models.py:164 pod/live/models.py:177 +#: pod/bbb/models.py:222 pod/live/admin.py:158 pod/live/models.py:212 +#: pod/live/models.py:251 pod/live/models.py:358 msgid "Broadcaster" msgstr "" @@ -513,7 +518,7 @@ msgstr "" #: pod/bbb/templates/bbb/list_meeting.html:14 #: pod/bbb/templates/bbb/list_meeting.html:18 #: pod/bbb/templates/bbb/publish_meeting.html:17 -#: pod/main/templates/navbar.html:116 +#: pod/main/templates/navbar.html:119 msgid "My BigBlueButton records" msgstr "" @@ -577,7 +582,7 @@ msgstr "" #: pod/bbb/templates/bbb/live_list_meeting.html:21 #: pod/bbb/templates/bbb/live_publish_meeting.html:18 #: pod/bbb/templates/bbb/live_publish_meeting.html:83 -#: pod/main/templates/navbar.html:118 +#: pod/main/templates/navbar.html:121 msgid "Perform a BigBlueButton live" msgstr "" @@ -671,6 +676,7 @@ msgstr "" #: pod/bbb/templates/bbb/live_record_list.html:18 #: pod/bbb/templates/bbb/record_list.html:18 +#: pod/live/templates/live/events_list.html:42 #: pod/playlist/templates/playlist/playlist_list.html:39 #: pod/recorder/templates/recorder/record_list.html:18 #: pod/video/templates/videos/video_list.html:18 @@ -720,24 +726,25 @@ msgid "File to import" msgstr "" #: pod/chapter/models.py:14 pod/completion/models.py:58 -#: pod/enrichment/models.py:112 pod/video/models.py:813 +#: pod/enrichment/models.py:112 pod/video/models.py:814 msgid "video" msgstr "" #: pod/chapter/models.py:15 pod/enrichment/models.py:113 -#: pod/recorder/models.py:297 pod/video/models.py:584 +#: pod/recorder/models.py:297 pod/video/models.py:585 msgid "title" msgstr "titel" #: pod/chapter/models.py:17 pod/enrichment/models.py:115 -#: pod/video/models.py:586 +#: pod/video/models.py:587 msgid "slug" msgstr "" #: pod/chapter/models.py:21 pod/completion/models.py:255 -#: pod/enrichment/models.py:119 pod/live/models.py:76 pod/playlist/models.py:18 -#: pod/video/models.py:272 pod/video/models.py:424 pod/video/models.py:550 -#: pod/video/models.py:590 pod/video/models.py:646 pod/video/models.py:1997 +#: pod/enrichment/models.py:119 pod/live/models.py:124 +#: pod/playlist/models.py:18 pod/video/models.py:273 pod/video/models.py:425 +#: pod/video/models.py:551 pod/video/models.py:591 pod/video/models.py:647 +#: pod/video/models.py:2012 msgid "" "Used to access this instance, the \"slug\" is a short label containing only " "letters, numbers, underscore or dash top." @@ -745,7 +752,7 @@ msgstr "" #: pod/chapter/models.py:28 pod/chapter/models.py:123 #: pod/chapter/templates/chapter/list_chapter.html:11 -#: pod/completion/models.py:262 +#: pod/completion/models.py:262 pod/live/models.py:346 msgid "Start time" msgstr "" @@ -830,10 +837,11 @@ msgstr "" #: pod/completion/models.py:249 #: pod/completion/templates/overlay/list_overlay.html:10 #: pod/enrichment/templates/enrichment/list_enrichment.html:10 -#: pod/main/models.py:124 pod/main/views.py:190 pod/playlist/models.py:12 +#: pod/live/models.py:296 pod/main/models.py:124 pod/main/views.py:190 +#: pod/playlist/models.py:12 #: pod/playlist/templates/playlist/playlist_element_list.html:9 -#: pod/video/models.py:258 pod/video/models.py:412 pod/video/models.py:544 -#: pod/video/models.py:633 pod/video/templates/channel/list_theme.html:10 +#: pod/video/models.py:259 pod/video/models.py:413 pod/video/models.py:545 +#: pod/video/models.py:634 pod/video/templates/channel/list_theme.html:10 msgid "Title" msgstr "" @@ -866,6 +874,8 @@ msgstr "" #: pod/chapter/templates/chapter/list_chapter.html:32 #: pod/enrichment/templates/enrichment/list_enrichment.html:35 +#: pod/live/templates/live/event_delete.html:9 +#: pod/live/templates/live/event_delete.html:71 #: pod/playlist/templates/playlist.html:70 #: pod/podfile/templates/podfile/list_folder_files.html:101 #: pod/podfile/templates/podfile/list_folder_files.html:296 @@ -955,51 +965,52 @@ msgstr "" msgid "Please correct errors." msgstr "" -#: pod/completion/models.py:25 +#: pod/completion/models.py:25 pod/custom/settings_local.py:909 msgid "actor" msgstr "" -#: pod/completion/models.py:26 +#: pod/completion/models.py:26 pod/custom/settings_local.py:910 msgid "author" msgstr "" -#: pod/completion/models.py:27 +#: pod/completion/models.py:27 pod/custom/settings_local.py:911 msgid "designer" msgstr "" -#: pod/completion/models.py:28 +#: pod/completion/models.py:28 pod/custom/settings_local.py:912 msgid "consultant" msgstr "" -#: pod/completion/models.py:29 +#: pod/completion/models.py:29 pod/custom/settings_local.py:913 msgid "contributor" msgstr "" #: pod/completion/models.py:30 pod/completion/tests/test_views.py:219 +#: pod/custom/settings_local.py:914 msgid "editor" msgstr "" -#: pod/completion/models.py:31 +#: pod/completion/models.py:31 pod/custom/settings_local.py:915 msgid "speaker" msgstr "" -#: pod/completion/models.py:32 +#: pod/completion/models.py:32 pod/custom/settings_local.py:916 msgid "soundman" msgstr "" -#: pod/completion/models.py:33 +#: pod/completion/models.py:33 pod/custom/settings_local.py:917 msgid "director" msgstr "" -#: pod/completion/models.py:34 +#: pod/completion/models.py:34 pod/custom/settings_local.py:918 msgid "writer" msgstr "" -#: pod/completion/models.py:35 +#: pod/completion/models.py:35 pod/custom/settings_local.py:919 msgid "technician" msgstr "" -#: pod/completion/models.py:36 +#: pod/completion/models.py:36 pod/custom/settings_local.py:920 msgid "voice-over" msgstr "" @@ -1060,14 +1071,14 @@ msgstr "" #: pod/completion/models.py:124 pod/completion/models.py:171 #: pod/completion/models.py:248 #: pod/completion/templates/video_caption_maker.html:69 -#: pod/enrichment/models.py:322 pod/enrichment/models.py:356 -#: pod/interactive/models.py:18 pod/interactive/models.py:48 -#: pod/playlist/models.py:85 +#: pod/custom/settings_local.py:795 pod/enrichment/models.py:322 +#: pod/enrichment/models.py:356 pod/interactive/models.py:18 +#: pod/interactive/models.py:48 pod/playlist/models.py:85 #: pod/playlist/templates/playlist/playlist_video_list.html:7 #: pod/playlist/templates/playlist/playlist_video_list.html:15 -#: pod/recorder/models.py:90 pod/video/models.py:627 pod/video/models.py:1326 -#: pod/video/models.py:1504 pod/video/models.py:1579 pod/video/models.py:1644 -#: pod/video/models.py:1701 pod/video/models.py:1724 pod/video/models.py:1756 +#: pod/recorder/models.py:90 pod/video/models.py:628 pod/video/models.py:1341 +#: pod/video/models.py:1519 pod/video/models.py:1594 pod/video/models.py:1659 +#: pod/video/models.py:1716 pod/video/models.py:1739 pod/video/models.py:1771 msgid "Video" msgstr "" @@ -1170,9 +1181,9 @@ msgstr "" msgid "left" msgstr "" -#: pod/completion/models.py:251 pod/live/models.py:72 pod/playlist/models.py:14 -#: pod/video/models.py:268 pod/video/models.py:421 pod/video/models.py:546 -#: pod/video/models.py:642 pod/video/models.py:1993 +#: pod/completion/models.py:251 pod/live/models.py:120 pod/live/models.py:289 +#: pod/playlist/models.py:14 pod/video/models.py:269 pod/video/models.py:422 +#: pod/video/models.py:547 pod/video/models.py:643 pod/video/models.py:2008 msgid "Slug" msgstr "" @@ -1180,7 +1191,7 @@ msgstr "" msgid "Start time of the overlay, in seconds." msgstr "" -#: pod/completion/models.py:267 +#: pod/completion/models.py:267 pod/live/models.py:351 msgid "End time" msgstr "" @@ -1669,6 +1680,16 @@ msgstr "" msgid "Please correct errors" msgstr "" +#: pod/custom/settings_local.py:796 pod/recorder/models.py:91 +msgid "Audiovideocast" +msgstr "" + +#: pod/custom/settings_local.py:797 pod/recorder/models.py:92 +#: pod/recorder/templates/recorder/opencast-studio.html:45 +#: pod/recorder/templates/recorder/opencast-studio.html:49 +msgid "Studio" +msgstr "" + #: pod/enrichment/apps.py:7 msgid "Enrichment version" msgstr "" @@ -1689,8 +1710,8 @@ msgstr "" #: pod/enrichment/forms.py:33 pod/enrichment/models.py:363 #: pod/interactive/forms.py:12 pod/interactive/models.py:55 -#: pod/podfile/models.py:33 pod/recorder/models.py:176 pod/video/models.py:341 -#: pod/video/models.py:767 +#: pod/live/models.py:172 pod/live/models.py:404 pod/podfile/models.py:33 +#: pod/recorder/models.py:176 pod/video/models.py:342 pod/video/models.py:768 msgid "Groups" msgstr "" @@ -1732,8 +1753,9 @@ msgstr "" #: pod/enrichment/models.py:141 #: pod/enrichment/templates/enrichment/list_enrichment.html:11 -#: pod/video/forms.py:115 pod/video/models.py:573 pod/video/models.py:653 -#: pod/video/views.py:1675 pod/video_search/templates/search/search.html:83 +#: pod/live/models.py:362 pod/video/forms.py:115 pod/video/models.py:574 +#: pod/video/models.py:654 pod/video/views.py:1675 +#: pod/video_search/templates/search/search.html:83 msgid "Type" msgstr "" @@ -2035,12 +2057,15 @@ msgid "Interactive video" msgstr "" #: pod/interactive/templates/interactive/video_interactive.html:47 +#: pod/live/templates/live/event-all-info.html:6 #: pod/video/templates/videos/video-all-info.html:6 msgid "Report the video" msgstr "" #: pod/interactive/templates/interactive/video_interactive.html:56 #: pod/interactive/templates/interactive/video_interactive.html:57 +#: pod/live/templates/live/event-all-info.html:16 +#: pod/live/templates/live/event-info.html:9 #: pod/video/templates/videos/video-all-info.html:21 #: pod/video/templates/videos/video-info.html:9 msgid "Summary" @@ -2048,6 +2073,8 @@ msgstr "" #: pod/interactive/templates/interactive/video_interactive.html:61 #: pod/interactive/templates/interactive/video_interactive.html:62 +#: pod/live/templates/live/event-all-info.html:22 +#: pod/live/templates/live/event-info.html:22 #: pod/video/templates/videos/video-all-info.html:27 #: pod/video/templates/videos/video-info.html:36 msgid "Infos" @@ -2062,6 +2089,8 @@ msgstr "" #: pod/interactive/templates/interactive/video_interactive.html:73 #: pod/interactive/templates/interactive/video_interactive.html:74 +#: pod/live/templates/live/event-all-info.html:33 +#: pod/live/templates/live/event-info.html:63 #: pod/video/templates/videos/video-all-info.html:44 #: pod/video/templates/videos/video-info.html:199 msgid "Embed/Share" @@ -2094,7 +2123,7 @@ msgid "Add the video to a playlist" msgstr "" #: pod/interactive/templates/interactive/video_interactive.html:101 -#: pod/main/templates/navbar.html:108 +#: pod/main/templates/navbar.html:111 #: pod/playlist/templates/my_playlists.html:10 #: pod/playlist/templates/playlist.html:14 pod/playlist/views.py:48 msgid "My playlists" @@ -2146,16 +2175,78 @@ msgstr "" msgid "You cannot add interactivity to this video." msgstr "" -#: pod/live/forms.py:50 pod/video/forms.py:291 pod/video/forms.py:855 +#: pod/live/admin.py:122 pod/live/models.py:423 pod/recorder/models.py:180 +#: pod/video/models.py:772 +msgid "password" +msgstr "" + +#: pod/live/admin.py:127 pod/video/admin.py:75 +msgid "Yes" +msgstr "" + +#: pod/live/admin.py:128 pod/video/admin.py:76 +msgid "No" +msgstr "" + +#: pod/live/admin.py:163 +msgid "Auto start admin" +msgstr "" + +#: pod/live/admin.py:183 pod/live/models.py:419 pod/video/models.py:784 +#: pod/video/models.py:917 +msgid "Thumbnails" +msgstr "" + +#: pod/live/forms.py:59 pod/live/models.py:183 +msgid "Piloting implementation" +msgstr "" + +#: pod/live/forms.py:60 pod/live/models.py:184 +msgid "Select the piloting implementation for to this broadcaster." +msgstr "" + +#: pod/live/forms.py:110 +msgid "End should not be in the past" +msgstr "" + +#: pod/live/forms.py:111 pod/live/models.py:275 +msgid "An event cannot be planned in the past" +msgstr "" + +#: pod/live/forms.py:114 pod/live/forms.py:115 +msgid "Start should not be after end" +msgstr "" + +#: pod/live/forms.py:132 +msgid "An event is already planned at these dates" +msgstr "" + +#: pod/live/forms.py:137 pod/video/forms.py:291 pod/video/forms.py:855 msgid "Password" msgstr "" -#: pod/live/models.py:30 pod/live/models.py:70 pod/recorder/models.py:103 +#: pod/live/forms.py:147 pod/live/models.py:71 pod/live/models.py:130 +msgid "Building" +msgstr "" + +#: pod/live/forms.py:154 +msgid "Broadcaster device" +msgstr "" + +#: pod/live/forms.py:297 pod/recorder/forms.py:75 pod/video/forms.py:864 +msgid "I agree" +msgstr "" + +#: pod/live/forms.py:298 +msgid "Delete event cannot be undo" +msgstr "" + +#: pod/live/models.py:49 pod/live/models.py:118 pod/recorder/models.py:103 msgid "name" msgstr "" -#: pod/live/models.py:36 pod/live/templates/live/live.html:153 -#: pod/video/models.py:292 pod/video/models.py:445 +#: pod/live/models.py:55 pod/live/templates/live/direct.html:158 +#: pod/video/models.py:293 pod/video/models.py:446 #: pod/video/templates/channel/channel.html:85 #: pod/video/templates/channel/channel.html:158 #: pod/video/templates/channel/channel.html:168 @@ -2166,235 +2257,747 @@ msgstr "" msgid "Headband" msgstr "" -#: pod/live/models.py:52 pod/live/models.py:82 -msgid "Building" -msgstr "" - -#: pod/live/models.py:53 +#: pod/live/models.py:72 msgid "Buildings" msgstr "" -#: pod/live/models.py:83 pod/recorder/models.py:105 +#: pod/live/models.py:131 pod/recorder/models.py:105 msgid "description" msgstr "" -#: pod/live/models.py:89 +#: pod/live/models.py:137 msgid "Poster" msgstr "" -#: pod/live/models.py:91 +#: pod/live/models.py:139 msgid "URL" msgstr "" -#: pod/live/models.py:91 +#: pod/live/models.py:139 msgid "Url of the stream" msgstr "" -#: pod/live/models.py:94 +#: pod/live/models.py:142 msgid "This video will be displayed when there is no live stream." msgstr "" -#: pod/live/models.py:97 +#: pod/live/models.py:145 msgid "Video on hold" msgstr "" -#: pod/live/models.py:100 +#: pod/live/models.py:149 +msgid "Check if the broadcaster is currently sending stream." +msgstr "" + +#: pod/live/models.py:153 +msgid "Enable viewers count" +msgstr "" + +#: pod/live/models.py:154 +msgid "Enable viewers count on live." +msgstr "" + +#: pod/live/models.py:158 +msgid "Live is accessible only to authenticated users." +msgstr "" + +#: pod/live/models.py:162 +msgid "Show in live tab" +msgstr "" + +#: pod/live/models.py:163 +msgid "Live is accessible from the Live tab" +msgstr "" + +#: pod/live/models.py:166 +msgid "Number of viewers" +msgstr "" + +#: pod/live/models.py:174 +msgid "Select one or more groups who can manage event to this broadcaster." +msgstr "" + +#: pod/live/models.py:190 +msgid "Piloting configuration parameters" +msgstr "" + +#: pod/live/models.py:191 +msgid "Add piloting configuration parameters in Json format." +msgstr "" + +#: pod/live/models.py:213 +msgid "Broadcasters" +msgstr "" + +#: pod/live/models.py:244 +msgid "Is recording ?" +msgstr "" + +#: pod/live/models.py:248 +msgid "Viewer" +msgstr "" + +#: pod/live/models.py:249 +msgid "Viewkey" +msgstr "" + +#: pod/live/models.py:253 +msgid "Last heartbeat" +msgstr "" + +#: pod/live/models.py:256 +msgid "Heartbeat" +msgstr "" + +#: pod/live/models.py:257 +msgid "Heartbeats" +msgstr "" + +#: pod/live/models.py:299 pod/video/models.py:637 +msgid "" +"Please choose a title as short and accurate as possible, reflecting the main " +"subject / context of the content. (max length: 250 characters)" +msgstr "" + +#: pod/live/models.py:306 pod/main/forms.py:62 pod/main/models.py:177 +#: pod/playlist/models.py:26 pod/video/forms.py:135 pod/video/forms.py:352 +#: pod/video/forms.py:403 pod/video/models.py:279 pod/video/models.py:431 +#: pod/video/models.py:677 pod/video/templates/channel/list_theme.html:11 +msgid "Description" +msgstr "" + +#: pod/live/models.py:310 pod/video/forms.py:138 pod/video/forms.py:355 +#: pod/video/forms.py:406 pod/video/models.py:283 pod/video/models.py:435 +#: pod/video/models.py:681 +msgid "" +"In this field you can describe your content, add all needed related " +"information, and format the result using the toolbar." +msgstr "" + +#: pod/live/models.py:319 pod/playlist/models.py:24 +#: pod/playlist/templates/playlist/playlist_element_list.html:10 +#: pod/podfile/models.py:28 pod/video/admin.py:194 pod/video/models.py:658 +#: pod/video_search/templates/search/search.html:71 +msgid "Owner" +msgstr "" + +#: pod/live/models.py:329 pod/video/forms.py:125 pod/video/models.py:667 +msgid "Additional owners" +msgstr "" + +#: pod/live/models.py:333 +msgid "" +"You can add additional owners to the event. They will have the same rights " +"as you except that they can't delete this event." +msgstr "" + +#: pod/live/models.py:340 pod/video/models.py:688 +msgid "Date of event" +msgstr "" + +#: pod/live/models.py:348 +msgid "Start time of the live event." +msgstr "" + +#: pod/live/models.py:353 +msgid "End time of the live event." +msgstr "" + +#: pod/live/models.py:359 +msgid "Broadcaster name." +msgstr "" + +#: pod/live/models.py:365 msgid "Embedded Site URL" msgstr "" -#: pod/live/models.py:101 +#: pod/live/models.py:366 msgid "Url of the embedded site to display" msgstr "" -#: pod/live/models.py:106 +#: pod/live/models.py:371 msgid "Embedded Site Height" msgstr "" -#: pod/live/models.py:109 +#: pod/live/models.py:374 msgid "Height of the embedded site (in pixels)" msgstr "" -#: pod/live/models.py:112 +#: pod/live/models.py:377 msgid "Embedded aside Site URL" msgstr "" -#: pod/live/models.py:113 +#: pod/live/models.py:378 msgid "Url of the embedded site to display on aside" msgstr "" -#: pod/live/models.py:119 -msgid "Check if the broadcaster is currently sending stream." +#: pod/live/models.py:384 pod/recorder/models.py:157 pod/video/forms.py:273 +#: pod/video/models.py:749 +msgid "Draft" msgstr "" -#: pod/live/models.py:123 -msgid "Enable viewers count" +#: pod/live/models.py:386 +msgid "" +"If this box is checked, the event will be visible only by you and the " +"additional owners but accessible to anyone having the url link." msgstr "" -#: pod/live/models.py:124 -msgid "Enable viewers count on live." +#: pod/live/models.py:395 +msgid "" +"If this box is checked, the event will only be accessible to authenticated " +"users." msgstr "" -#: pod/live/models.py:128 -msgid "Live is accessible only to authenticated users." +#: pod/live/models.py:405 +msgid "Select one or more groups who can access to this event" msgstr "" -#: pod/live/models.py:132 -msgid "Show in live tab" +#: pod/live/models.py:409 +msgid "Auto start" msgstr "" -#: pod/live/models.py:133 -msgid "Live is accessible from the Live tab" +#: pod/live/models.py:410 +msgid "If this box is checked, the record will start automatically." msgstr "" -#: pod/live/models.py:137 pod/recorder/models.py:180 pod/video/models.py:771 -msgid "password" +#: pod/live/models.py:424 +msgid "Viewing this event will not be possible without this password." msgstr "" -#: pod/live/models.py:138 -msgid "Viewing this live will not be possible without this password." +#: pod/live/models.py:436 +msgid "Event" msgstr "" -#: pod/live/models.py:143 -msgid "Number of viewers" +#: pod/live/models.py:437 pod/live/templates/live/event.html:26 +#: pod/live/templates/live/events.html:9 pod/live/templates/live/events.html:12 +msgid "Events" msgstr "" -#: pod/live/models.py:165 -msgid "Broadcasters" +#: pod/live/templates/live/direct.html:22 +#: pod/live/templates/live/direct.html:26 +#: pod/live/templates/live/directs.html:11 +#: pod/live/templates/live/directs_all.html:15 +#: pod/live/templates/live/directs_all.html:18 +#: pod/live/templates/live/directs_all.html:21 +msgid "Lives" msgstr "" -#: pod/live/models.py:174 -msgid "Viewer" +#: pod/live/templates/live/direct.html:36 +#: pod/live/templates/live/direct.html:39 +#: pod/live/templates/live/event_edit.html:16 +#: pod/live/templates/live/event_edit.html:21 +#: pod/live/templates/live/event_edit.html:36 +#: pod/live/templates/live/events.html:38 +#: pod/live/templates/live/events.html:41 +#: pod/live/templates/live/my_events.html:50 +#: pod/live/templates/live/my_events.html:53 +msgid "Plan an event" msgstr "" -#: pod/live/models.py:175 -msgid "Viewkey" +#: pod/live/templates/live/direct.html:83 +msgid "Send message" msgstr "" -#: pod/live/models.py:179 -msgid "Last heartbeat" +#: pod/live/templates/live/direct.html:85 +msgid "" +"You can send a message (100 characters maximum) to the BigBlueButton " +"session. It will be displayed within 15 to 30 seconds on the live video." msgstr "" -#: pod/live/models.py:182 -msgid "Heartbeat" +#: pod/live/templates/live/direct.html:86 +msgid "Message" msgstr "" -#: pod/live/models.py:183 -msgid "Heartbeats" +#: pod/live/templates/live/direct.html:89 +msgid "You must be authenticated to send a message." msgstr "" -#: pod/live/templates/live/building.html:11 -#: pod/live/templates/live/live.html:22 pod/live/templates/live/live.html:26 -#: pod/live/templates/live/lives.html:15 pod/live/templates/live/lives.html:18 -#: pod/live/templates/live/lives.html:21 pod/main/templates/navbar.html:42 -msgid "Lives" +#: pod/live/templates/live/direct.html:95 pod/main/templates/aside.html:32 +#: pod/main/templates/aside.html:53 +msgid "Submit" +msgstr "" + +#: pod/live/templates/live/direct.html:111 +msgid "Recording in progress" +msgstr "" + +#: pod/live/templates/live/direct.html:117 +msgid "No recording in progress" +msgstr "" + +#: pod/live/templates/live/direct.html:126 +#: pod/live/templates/live/events_next.html:7 +msgid "Next events" +msgstr "" + +#: pod/live/templates/live/direct.html:139 +msgid "Manage live" msgstr "" -#: pod/live/templates/live/building.html:38 -#: pod/live/templates/live/live.html:164 pod/live/templates/live/lives.html:41 +#: pod/live/templates/live/direct.html:141 +msgid "Edit the live" +msgstr "" + +#: pod/live/templates/live/direct.html:165 +#: pod/live/templates/live/directs_all.html:39 +msgid "no broadcast in progress" +msgstr "" + +#: pod/live/templates/live/direct.html:169 +#: pod/live/templates/live/directs.html:38 +#: pod/live/templates/live/directs_all.html:41 msgid "Sorry, no lives found" msgstr "" -#: pod/live/templates/live/live-form.html:3 -msgid "This live is protected by password, please fill in and click send." +#: pod/live/templates/live/direct.html:173 +#: pod/live/templates/live/direct.html:174 +#: pod/live/templates/live/directs_all.html:45 +#: pod/live/templates/live/directs_all.html:46 +msgid "access map" +msgstr "" + +#: pod/live/templates/live/direct.html:204 +#: pod/live/templates/live/event.html:187 +#: pod/video/templates/videos/video-script.html:28 +msgid "Please use different browser" +msgstr "" + +#: pod/live/templates/live/direct.html:241 +#: pod/live/templates/live/event.html:229 +msgid "" +"Thank you for watching this streaming live with us. The page will reload " +"automatically within a few seconds to display the video on hold." +msgstr "" + +#: pod/live/templates/live/direct.html:258 +#: pod/live/templates/live/event.html:253 +msgid "Live not found, displaying the video on hold, retry every 10 seconds" +msgstr "" + +#: pod/live/templates/live/direct.html:273 +#: pod/live/templates/live/event.html:276 +msgid "Live not found, retry in 10 seconds" +msgstr "" + +#: pod/live/templates/live/direct.html:329 +msgid "Message sent" +msgstr "" + +#: pod/live/templates/live/direct.html:330 +msgid "Message not sent: no broadcaster found" +msgstr "" + +#: pod/live/templates/live/direct.html:331 +msgid "Message not sent: connection problem (REDIS)" +msgstr "" + +#: pod/live/templates/live/directs_all.html:53 +msgid "Sorry, no buildings found" msgstr "" -#: pod/live/templates/live/live-form.html:8 +#: pod/live/templates/live/event-form.html:3 +msgid "This event is protected by password, please fill in and click send." +msgstr "" + +#: pod/live/templates/live/event-form.html:8 #: pod/video/templates/videos/video-form.html:8 msgid "Password required" msgstr "" -#: pod/live/templates/live/live-form.html:31 +#: pod/live/templates/live/event-form.html:31 +#: pod/live/templates/live/event_edit.html:93 +#: pod/live/templates/live/filter_aside.html:13 #: pod/main/templates/contact_us.html:46 #: pod/video/templates/videos/video-form.html:31 #: pod/video_search/templates/search/search.html:60 msgid "Send" msgstr "" -#: pod/live/templates/live/live.html:102 -msgid "Send message" +#: pod/live/templates/live/event-iframe.html:66 +#: pod/live/templates/live/event.html:115 +msgid "Event is finished at : " +msgstr "" + +#: pod/live/templates/live/event-iframe.html:71 +#: pod/live/templates/live/event.html:120 +msgid "The event is scheduled on the : " +msgstr "" + +#: pod/live/templates/live/event-iframe.html:72 +#: pod/live/templates/live/event.html:121 +msgid "from" +msgstr "" + +#: pod/live/templates/live/event-iframe.html:72 +#: pod/live/templates/live/event.html:121 +msgid "to" +msgstr "" + +#: pod/live/templates/live/event-info.html:26 +msgid "Broadcasted on:" +msgstr "" + +#: pod/live/templates/live/event-info.html:27 +#, python-format +msgid "%(start_date)s from %(start_time)s to %(end_time)s" +msgstr "" + +#: pod/live/templates/live/event-info.html:31 +msgid "Location:" +msgstr "" + +#: pod/live/templates/live/event-info.html:36 +#: pod/video/templates/videos/video-info.html:39 +msgid "Added by:" +msgstr "" + +#: pod/live/templates/live/event-info.html:55 +#: pod/video/templates/videos/video-info.html:120 +msgid "Type:" +msgstr "" + +#: pod/live/templates/live/event-info.html:63 +#: pod/video/templates/videos/video-info.html:199 +#: pod/video/templates/videos/video_edit.html:159 +msgid "Embed/Share (Draft Mode)" +msgstr "" + +#: pod/live/templates/live/event-info.html:68 +msgid "" +"Please note that your event is in draft mode.
      The following links " +"contain a key allowing access. Anyone with this links can access it." +msgstr "" + +#: pod/live/templates/live/event-info.html:76 +#: pod/video/templates/videos/video-info.html:260 +msgid "Embed in a web page" +msgstr "" + +#: pod/live/templates/live/event-info.html:78 +#: pod/video/templates/videos/video-info.html:262 +msgid "Copy the content of this text box and paste it in the page:" +msgstr "" + +#: pod/live/templates/live/event-info.html:84 +#: pod/video/templates/videos/video-info.html:202 +msgid "Social Networks" +msgstr "" + +#: pod/live/templates/live/event-info.html:87 +#: pod/live/templates/live/event-info.html:88 +#: pod/live/templates/live/event-info.html:89 pod/main/templates/aside.html:11 +#: pod/main/templates/aside.html:12 pod/main/templates/aside.html:13 +#: pod/video/templates/videos/video-info.html:205 +#: pod/video/templates/videos/video-info.html:206 +#: pod/video/templates/videos/video-info.html:207 +msgid "Share on" +msgstr "" + +#: pod/live/templates/live/event-info.html:97 +#: pod/video/templates/videos/video-info.html:267 +msgid "Share the link" +msgstr "" + +#: pod/live/templates/live/event-info.html:99 +#: pod/video/templates/videos/video-info.html:269 +msgid "Use this link to share the video:" +msgstr "" + +#: pod/live/templates/live/event-info.html:103 +#: pod/video/templates/videos/video-info.html:273 +msgid "QR code for this link:" +msgstr "" + +#: pod/live/templates/live/event.html:24 +#: pod/live/templates/live/event_delete.html:7 +#: pod/live/templates/live/event_edit.html:9 +#: pod/live/templates/live/my_events.html:13 pod/main/templates/navbar.html:106 +msgid "My events" +msgstr "" + +#: pod/live/templates/live/event.html:90 +msgid "Start record" +msgstr "" + +#: pod/live/templates/live/event.html:92 +msgid "Stop record" +msgstr "" + +#: pod/live/templates/live/event.html:94 +msgid "Split record" +msgstr "" + +#: pod/live/templates/live/event.html:129 +msgid "Current event videos" +msgstr "" + +#: pod/live/templates/live/event.html:148 +#: pod/live/templates/live/event_edit.html:105 +msgid "Manage event" +msgstr "" + +#: pod/live/templates/live/event.html:151 +#: pod/live/templates/live/event_edit.html:109 +msgid "Edit the event" +msgstr "" + +#: pod/live/templates/live/event.html:158 +#: pod/live/templates/live/event_edit.html:116 +msgid "Delete the event" +msgstr "" + +#: pod/live/templates/live/event.html:469 +msgid "Recording duration" +msgstr "" + +#: pod/live/templates/live/event_card.html:21 +#: pod/playlist/templates/playlist/playlist_video_card.html:13 +#: pod/video/templates/videos/card.html:10 +msgid "This content is password protected." +msgstr "" + +#: pod/live/templates/live/event_card.html:26 +#: pod/playlist/templates/playlist/playlist_video_card.html:19 +#: pod/video/templates/videos/card.html:16 +msgid "This content is in draft." +msgstr "" + +#: pod/live/templates/live/event_card.html:59 +#, python-format +msgid "The %(start_date)s from %(start_time)s to %(end_time)s" +msgstr "" + +#: pod/live/templates/live/event_delete.html:13 +#: pod/live/templates/live/event_delete.html:17 +#, python-format +msgid "Deleting the event \"%(vtitle)s\"" +msgstr "" + +#: pod/live/templates/live/event_delete.html:19 +msgid "View the event" +msgstr "" + +#: pod/live/templates/live/event_delete.html:21 +#: pod/live/templates/live/event_delete.html:72 +msgid "Back to the event" +msgstr "" + +#: pod/live/templates/live/event_delete.html:28 +msgid "The event is currently in progress. Deletion is not possible." +msgstr "" + +#: pod/live/templates/live/event_delete.html:32 +msgid "To delete the event, please checked in and click send." +msgstr "" + +#: pod/live/templates/live/event_delete.html:37 +#: pod/recorder/templates/recorder/record_delete.html:29 +#: pod/video/templates/videos/video_delete.html:30 +msgid "Agree required" +msgstr "" + +#: pod/live/templates/live/event_edit.html:14 +#: pod/live/templates/live/event_edit.html:20 +#: pod/live/templates/live/event_edit.html:34 +msgid "Editing the event" +msgstr "" + +#: pod/live/templates/live/event_edit.html:42 +msgid "" +"Access to adding event has been restricted. If you want to add events on the " +"platform, please" +msgstr "" + +#: pod/live/templates/live/event_edit.html:43 +#: pod/video/templates/videos/add_video.html:31 +#: pod/video/templates/videos/video_edit.html:48 +msgid "contact us" +msgstr "" + +#: pod/live/templates/live/event_edit.html:56 +msgid "The event is currently in progress. Editing options are limited." +msgstr "" + +#: pod/live/templates/live/event_edit.html:91 +#: pod/live/templates/live/event_edit.html:154 +msgid "Display advanced options" +msgstr "" + +#: pod/live/templates/live/event_edit.html:123 +msgid "Event planning" +msgstr "" + +#: pod/live/templates/live/event_edit.html:125 +msgid "" +"You can schedule a live event by selecting a building and a room or " +"recording device." +msgstr "" + +#: pod/live/templates/live/event_edit.html:126 +msgid "You will then need to specify a date, a start time and an end time." +msgstr "" + +#: pod/live/templates/live/event_edit.html:127 +msgid "" +"Please note that 2 events cannot be scheduled in the same room " +"simultaneously." +msgstr "" + +#: pod/live/templates/live/event_edit.html:128 +msgid "Finally, remember to provide as precise a description as possible." +msgstr "" + +#: pod/live/templates/live/event_edit.html:132 +#: pod/main/templates/contact_us.html:63 +#: pod/video/templates/channel/channel_edit.html:90 +#: pod/video/templates/channel/theme_edit.html:66 +#: pod/video/templates/videos/video_edit.html:186 +msgid "Mandatory fields" +msgstr "" + +#: pod/live/templates/live/event_edit.html:134 +#: pod/main/templates/contact_us.html:65 +#: pod/video/templates/channel/channel_edit.html:92 +#: pod/video/templates/channel/theme_edit.html:68 +#: pod/video/templates/videos/video_edit.html:188 +msgid "Fields marked with an asterisk are mandatory." +msgstr "" + +#: pod/live/templates/live/event_edit.html:158 +msgid "Hide advanced options" +msgstr "" + +#: pod/live/templates/live/events.html:28 +#: pod/live/templates/live/my_events.html:16 +#: pod/live/templates/live/my_events.html:47 +#, python-format +msgid "%(counter)s event found" +msgid_plural "%(counter)s events found" +msgstr[0] "" +msgstr[1] "" + +#: pod/live/templates/live/events.html:30 +#: pod/live/templates/live/events.html:33 +msgid "Supervise broadcasters" msgstr "" -#: pod/live/templates/live/live.html:104 +#: pod/live/templates/live/events.html:48 +#: pod/live/templates/live/my_events.html:61 msgid "" -"You can send a message (100 characters maximum) to the BigBlueButton " -"session. It will be displayed within 15 to 30 seconds on the live video." +"Please use the thumbnails toolbar which is located under the event on which " +"you want to work with." msgstr "" -#: pod/live/templates/live/live.html:105 -msgid "Message" +#: pod/live/templates/live/events_list.html:13 +msgid "Sorry, no event found." msgstr "" -#: pod/live/templates/live/live.html:108 -msgid "You must be authenticated to send a message." +#: pod/live/templates/live/events_list.html:26 +#: pod/video/templates/videos/category_modal.html:30 +#: pod/video/templates/videos/paginator.html:7 +msgid "Previous page" msgstr "" -#: pod/live/templates/live/live.html:114 pod/main/templates/aside.html:32 -#: pod/main/templates/aside.html:53 -msgid "Submit" +#: pod/live/templates/live/events_list.html:36 +#: pod/video/templates/videos/category_modal.html:35 +#: pod/video/templates/videos/paginator.html:21 +msgid "Next page" msgstr "" -#: pod/live/templates/live/live.html:134 -msgid "Manage live" +#: pod/live/templates/live/events_next.html:14 +msgid "Show all events" msgstr "" -#: pod/live/templates/live/live.html:136 -msgid "Edit the live" +#: pod/live/templates/live/filter_aside.html:8 +#: pod/video/templates/videos/filter_aside.html:8 +#: pod/video/templates/videos/filter_aside_category.html:6 +msgid "Filters" msgstr "" -#: pod/live/templates/live/live.html:160 pod/live/templates/live/lives.html:39 -msgid "no broadcast in progress" +#: pod/live/templates/live/filter_aside.html:12 +#: pod/main/templates/aside.html:42 pod/main/templates/aside.html:48 +#: pod/main/templates/navbar.html:37 pod/video/models.py:575 +#: pod/video/templates/videos/filter_aside.html:36 +msgid "Types" msgstr "" -#: pod/live/templates/live/live.html:168 pod/live/templates/live/live.html:169 -#: pod/live/templates/live/lives.html:45 pod/live/templates/live/lives.html:46 -msgid "access map" +#: pod/live/templates/live/my_events.html:45 +msgid "No event found" msgstr "" -#: pod/live/templates/live/live.html:199 -#: pod/video/templates/videos/video-script.html:28 -msgid "Please use different browser" +#: pod/live/templates/live/my_events.html:59 +msgid "" +"You have not planned any event yet, please use the \"Plan an event\" button " +"to add one" msgstr "" -#: pod/live/templates/live/live.html:236 -msgid "" -"Thank you for watching this streaming live with us. The page will reload " -"automatically within a few seconds to display the video on hold." +#: pod/live/templates/live/my_events.html:63 +msgid "Coming events" msgstr "" -#: pod/live/templates/live/live.html:253 -msgid "Live not found, displaying the video on hold, retry every 10 seconds" +#: pod/live/templates/live/my_events.html:66 +msgid "Past events" msgstr "" -#: pod/live/templates/live/live.html:268 -msgid "Live not found, retry in 10 seconds" +#: pod/live/utils.py:50 +#, python-format +msgid "Registration of event #%(content_id)s" msgstr "" -#: pod/live/templates/live/live.html:324 -msgid "Message sent" +#: pod/live/utils.py:58 pod/live/utils.py:79 pod/video/utils.py:199 +#: pod/video/utils.py:224 pod/video/utils.py:292 pod/video/utils.py:317 +msgid "Hello," msgstr "" -#: pod/live/templates/live/live.html:325 -msgid "Message not sent: no broadcaster found" +#: pod/live/utils.py:60 pod/live/utils.py:81 +#, python-format +msgid "" +"You have just scheduled a new event called “%(content_title)s” in date of " +"%(start_date)s from %(start_time)s to %(end_time)s on video server : " +"%(url_event)s). You can find the other sharing options in the dedicated tab." msgstr "" -#: pod/live/templates/live/live.html:326 -msgid "Message not sent: connection problem (REDIS)" +#: pod/live/utils.py:70 pod/live/utils.py:91 pod/video/utils.py:207 +#: pod/video/utils.py:236 pod/video/utils.py:300 pod/video/utils.py:329 +msgid "Regards." msgstr "" -#: pod/live/templates/live/lives.html:53 -msgid "Sorry, no buildings found" +#: pod/live/utils.py:74 pod/live/utils.py:146 pod/video/utils.py:210 +#: pod/video/utils.py:240 pod/video/utils.py:303 pod/video/utils.py:333 +msgid "Post by:" msgstr "" -#: pod/live/views.py:58 +#: pod/live/views.py:69 pod/live/views.py:95 pod/live/views.py:104 msgid "You cannot view this page." msgstr "" -#: pod/live/views.py:97 +#: pod/live/views.py:270 +msgid "You cannot watch this event." +msgstr "" + +#: pod/live/views.py:285 #: pod/playlist/templates/playlist/playlist_video_list.html:73 #: pod/video/views.py:871 msgid "The password is incorrect." msgstr "" +#: pod/live/views.py:482 pod/video/views.py:361 pod/video/views.py:947 +msgid "The changes have been saved." +msgstr "" + +#: pod/live/views.py:502 +msgid "You cannot delete this event." +msgstr "" + +#: pod/live/views.py:511 +msgid "The event has been deleted." +msgstr "" + +#: pod/live/views.py:787 +#, python-format +msgid "Record the %(start_date)s from %(start_time)s to %(end_time)s" +msgstr "" + #: pod/lti/templates/lti_provider/assignment.html:28 msgid "Your browser does not support iframes" msgstr "" @@ -2436,8 +3039,8 @@ msgid "Other (please specify)" msgstr "" #: pod/main/forms.py:50 pod/podfile/models.py:25 pod/podfile/models.py:117 -#: pod/podfile/templates/podfile/home_content.html:32 pod/video/models.py:1497 -#: pod/video/models.py:1572 pod/video/models.py:1637 +#: pod/podfile/templates/podfile/home_content.html:32 pod/video/models.py:1512 +#: pod/video/models.py:1587 pod/video/models.py:1652 msgid "Name" msgstr "" @@ -2449,13 +3052,6 @@ msgstr "" msgid "Please choose a subject related to your request" msgstr "" -#: pod/main/forms.py:62 pod/main/models.py:177 pod/playlist/models.py:26 -#: pod/video/forms.py:135 pod/video/forms.py:352 pod/video/forms.py:403 -#: pod/video/models.py:278 pod/video/models.py:430 pod/video/models.py:676 -#: pod/video/templates/channel/list_theme.html:11 -msgid "Description" -msgstr "" - #: pod/main/forms.py:63 msgid "Provide a full description for your request" msgstr "" @@ -3165,57 +3761,43 @@ msgstr "" msgid "Share" msgstr "" -#: pod/main/templates/aside.html:11 pod/main/templates/aside.html:12 -#: pod/main/templates/aside.html:13 -#: pod/video/templates/videos/video-info.html:205 -#: pod/video/templates/videos/video-info.html:206 -#: pod/video/templates/videos/video-info.html:207 -msgid "Share on" -msgstr "" - #: pod/main/templates/aside.html:21 pod/main/templates/aside.html:27 -#: pod/recorder/models.py:213 pod/video/forms.py:181 pod/video/models.py:614 -#: pod/video/models.py:716 pod/video/templates/videos/filter_aside.html:56 +#: pod/recorder/models.py:213 pod/video/forms.py:181 pod/video/models.py:615 +#: pod/video/models.py:717 pod/video/templates/videos/filter_aside.html:56 msgid "Disciplines" msgstr "" -#: pod/main/templates/aside.html:42 pod/main/templates/aside.html:48 -#: pod/main/templates/navbar.html:37 pod/video/models.py:574 -#: pod/video/templates/videos/filter_aside.html:36 -msgid "Types" -msgstr "" - #: pod/main/templates/aside.html:62 pod/recorder/models.py:210 -#: pod/video/forms.py:172 pod/video/models.py:713 +#: pod/video/forms.py:172 pod/video/models.py:714 #: pod/video/templates/videos/filter_aside.html:76 #: pod/video_search/templates/search/search.html:95 msgid "Tags" msgstr "" -#: pod/main/templates/base.html:79 +#: pod/main/templates/base.html:82 msgid "Breadcrumb" msgstr "" -#: pod/main/templates/base.html:82 +#: pod/main/templates/base.html:85 #: pod/playlist/templates/playlist_player.html:16 msgid "Home" msgstr "" -#: pod/main/templates/base.html:87 +#: pod/main/templates/base.html:90 msgid "Toggle side Menu" msgstr "" -#: pod/main/templates/base.html:143 +#: pod/main/templates/base.html:149 msgid "" "We use third party cookies to personalize content, manage session and " "analyze site traffic." msgstr "" -#: pod/main/templates/base.html:145 +#: pod/main/templates/base.html:151 msgid "Learn more" msgstr "" -#: pod/main/templates/base.html:147 +#: pod/main/templates/base.html:153 msgid "I understand" msgstr "" @@ -3223,20 +3805,6 @@ msgstr "" msgid "Back to the previous page" msgstr "" -#: pod/main/templates/contact_us.html:63 -#: pod/video/templates/channel/channel_edit.html:90 -#: pod/video/templates/channel/theme_edit.html:66 -#: pod/video/templates/videos/video_edit.html:186 -msgid "Mandatory fields" -msgstr "" - -#: pod/main/templates/contact_us.html:65 -#: pod/video/templates/channel/channel_edit.html:92 -#: pod/video/templates/channel/theme_edit.html:68 -#: pod/video/templates/videos/video_edit.html:188 -msgid "Fields marked with an asterisk are mandatory." -msgstr "" - #: pod/main/templates/footer.html:29 msgid "" "Esup Portal: Community of French higher education establishments for digital " @@ -3316,10 +3884,14 @@ msgid "Toggle navigation" msgstr "" #: pod/main/templates/navbar.html:21 pod/recorder/models.py:223 -#: pod/video/forms.py:254 pod/video/models.py:356 pod/video/models.py:726 +#: pod/video/forms.py:254 pod/video/models.py:357 pod/video/models.py:727 msgid "Channels" msgstr "" +#: pod/main/templates/navbar.html:42 +msgid "Live events" +msgstr "" + #: pod/main/templates/navbar.html:63 pod/main/templates/navbar.html:65 #: pod/main/templates/navbar_collapse.html:136 #: pod/main/templates/navbar_collapse.html:137 @@ -3348,7 +3920,7 @@ msgstr "" msgid "Video Record" msgstr "" -#: pod/main/templates/navbar.html:106 +#: pod/main/templates/navbar.html:109 #: pod/video/templates/channel/channel_edit.html:17 #: pod/video/templates/channel/my_channels.html:6 #: pod/video/templates/channel/my_channels.html:9 @@ -3356,27 +3928,27 @@ msgstr "" msgid "My channels" msgstr "" -#: pod/main/templates/navbar.html:110 pod/podfile/templates/podfile/home.html:6 +#: pod/main/templates/navbar.html:113 pod/podfile/templates/podfile/home.html:6 #: pod/podfile/templates/podfile/home.html:10 pod/podfile/views.py:103 msgid "My files" msgstr "" -#: pod/main/templates/navbar.html:113 +#: pod/main/templates/navbar.html:116 #: pod/recorder/templates/recorder/claim_record.html:12 #: pod/recorder/templates/recorder/record_delete.html:7 #: pod/recorder/templates/recorder/record_delete.html:19 msgid "Claim a record" msgstr "" -#: pod/main/templates/navbar.html:124 +#: pod/main/templates/navbar.html:127 msgid "Log out" msgstr "" -#: pod/main/templates/navbar.html:161 pod/main/templates/navbar.html:163 +#: pod/main/templates/navbar.html:164 pod/main/templates/navbar.html:166 msgid "Dark mode" msgstr "" -#: pod/main/templates/navbar.html:172 pod/main/templates/navbar.html:174 +#: pod/main/templates/navbar.html:175 pod/main/templates/navbar.html:177 msgid "Dyslexia mode" msgstr "" @@ -3430,18 +4002,11 @@ msgstr "" msgid "your message untitled" msgstr "" -#: pod/playlist/models.py:24 -#: pod/playlist/templates/playlist/playlist_element_list.html:10 -#: pod/podfile/models.py:28 pod/video/admin.py:193 pod/video/models.py:657 -#: pod/video_search/templates/search/search.html:71 -msgid "Owner" -msgstr "" - #: pod/playlist/models.py:30 msgid "Short description of the playlist." msgstr "" -#: pod/playlist/models.py:33 pod/video/models.py:331 +#: pod/playlist/models.py:33 pod/video/models.py:332 msgid "Visible" msgstr "" @@ -3557,7 +4122,7 @@ msgid "Thumbnail" msgstr "" #: pod/playlist/templates/playlist/playlist_element_list.html:11 -#: pod/video/feeds.py:201 pod/video/models.py:785 pod/video/models.py:932 +#: pod/video/feeds.py:201 pod/video/models.py:786 pod/video/models.py:940 msgid "Duration" msgstr "" @@ -3585,16 +4150,6 @@ msgstr "" msgid "Sorry, no playlist found" msgstr "" -#: pod/playlist/templates/playlist/playlist_video_card.html:13 -#: pod/video/templates/videos/card.html:10 -msgid "This content is password protected." -msgstr "" - -#: pod/playlist/templates/playlist/playlist_video_card.html:19 -#: pod/video/templates/videos/card.html:16 -msgid "This content is in draft." -msgstr "" - #: pod/playlist/templates/playlist/playlist_video_card.html:24 #: pod/video/templates/videos/card.html:21 msgid "This content is chaptered." @@ -3846,70 +4401,56 @@ msgstr "" msgid "If checked, record will be deleted instead of saving it" msgstr "" -#: pod/recorder/forms.py:75 pod/video/forms.py:864 -msgid "I agree" -msgstr "" - #: pod/recorder/forms.py:76 msgid "Delete this record cannot be undo" msgstr "" -#: pod/recorder/models.py:32 pod/video/models.py:72 pod/video/views.py:147 +#: pod/recorder/models.py:32 pod/video/models.py:73 pod/video/views.py:147 msgid "None / All" msgstr "" -#: pod/recorder/models.py:33 pod/video/models.py:73 pod/video/views.py:148 +#: pod/recorder/models.py:33 pod/video/models.py:74 pod/video/views.py:148 msgid "Bachelor’s Degree" msgstr "" -#: pod/recorder/models.py:34 pod/video/models.py:74 pod/video/views.py:149 +#: pod/recorder/models.py:34 pod/video/models.py:75 pod/video/views.py:149 msgid "Master’s Degree" msgstr "" -#: pod/recorder/models.py:35 pod/video/models.py:75 pod/video/views.py:150 +#: pod/recorder/models.py:35 pod/video/models.py:76 pod/video/views.py:150 msgid "Doctorate" msgstr "" -#: pod/recorder/models.py:36 pod/video/models.py:76 pod/video/views.py:151 +#: pod/recorder/models.py:36 pod/video/models.py:77 pod/video/views.py:151 msgid "Other" msgstr "" -#: pod/recorder/models.py:43 pod/video/forms.py:201 pod/video/models.py:88 +#: pod/recorder/models.py:43 pod/video/forms.py:201 pod/video/models.py:89 msgid "Attribution 4.0 International (CC BY 4.0)" msgstr "" -#: pod/recorder/models.py:46 pod/video/forms.py:208 pod/video/models.py:91 +#: pod/recorder/models.py:46 pod/video/forms.py:208 pod/video/models.py:92 msgid "Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)" msgstr "" -#: pod/recorder/models.py:51 pod/video/forms.py:218 pod/video/models.py:96 +#: pod/recorder/models.py:51 pod/video/forms.py:218 pod/video/models.py:97 msgid "" "Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)" msgstr "" -#: pod/recorder/models.py:57 pod/video/forms.py:228 pod/video/models.py:102 +#: pod/recorder/models.py:57 pod/video/forms.py:228 pod/video/models.py:103 msgid "Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)" msgstr "" -#: pod/recorder/models.py:62 pod/video/forms.py:238 pod/video/models.py:107 +#: pod/recorder/models.py:62 pod/video/forms.py:238 pod/video/models.py:108 msgid "" "Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)" msgstr "" -#: pod/recorder/models.py:66 pod/video/forms.py:248 pod/video/models.py:111 +#: pod/recorder/models.py:66 pod/video/forms.py:248 pod/video/models.py:112 msgid "Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)" msgstr "" -#: pod/recorder/models.py:91 -msgid "Audiovideocast" -msgstr "" - -#: pod/recorder/models.py:92 -#: pod/recorder/templates/recorder/opencast-studio.html:45 -#: pod/recorder/templates/recorder/opencast-studio.html:49 -msgid "Studio" -msgstr "" - #: pod/recorder/models.py:108 msgid "Address IP" msgstr "" @@ -3952,102 +4493,98 @@ msgstr "" msgid "Video type by default." msgstr "" -#: pod/recorder/models.py:157 pod/video/forms.py:273 pod/video/models.py:748 -msgid "Draft" -msgstr "" - -#: pod/recorder/models.py:159 pod/video/models.py:750 +#: pod/recorder/models.py:159 pod/video/models.py:751 msgid "" "If this box is checked, the video will be visible and accessible only by you " "and the additional owners." msgstr "" -#: pod/recorder/models.py:168 pod/video/models.py:759 +#: pod/recorder/models.py:168 pod/video/models.py:760 msgid "" "If this box is checked, the video will only be accessible to authenticated " "users." msgstr "" -#: pod/recorder/models.py:177 pod/video/models.py:768 +#: pod/recorder/models.py:177 pod/video/models.py:769 msgid "Select one or more groups who can access to this video" msgstr "" -#: pod/recorder/models.py:181 pod/video/models.py:772 +#: pod/recorder/models.py:181 pod/video/models.py:773 msgid "Viewing this video will not be possible without this password." msgstr "" -#: pod/recorder/models.py:187 pod/video/forms.py:154 pod/video/models.py:690 +#: pod/recorder/models.py:187 pod/video/forms.py:154 pod/video/models.py:691 #: pod/video/templates/videos/filter_aside.html:100 #: pod/video_search/templates/search/search.html:146 msgid "University course" msgstr "" -#: pod/recorder/models.py:191 pod/video/forms.py:157 pod/video/models.py:694 +#: pod/recorder/models.py:191 pod/video/forms.py:157 pod/video/models.py:695 msgid "Select an university course as audience target of the content." msgstr "" -#: pod/recorder/models.py:194 pod/video/forms.py:168 pod/video/models.py:697 +#: pod/recorder/models.py:194 pod/video/forms.py:168 pod/video/models.py:698 #: pod/video_search/templates/search/search.html:133 msgid "Main language" msgstr "" -#: pod/recorder/models.py:198 pod/video/forms.py:169 pod/video/models.py:701 +#: pod/recorder/models.py:198 pod/video/forms.py:169 pod/video/models.py:702 msgid "Select the main language used in the content." msgstr "" -#: pod/recorder/models.py:201 pod/video/forms.py:312 pod/video/models.py:704 +#: pod/recorder/models.py:201 pod/video/forms.py:312 pod/video/models.py:705 #: pod/video/templates/videos/add_video.html:50 #: pod/video/templates/videos/add_video.html:94 msgid "Transcript" msgstr "" -#: pod/recorder/models.py:203 pod/video/models.py:706 +#: pod/recorder/models.py:203 pod/video/models.py:707 #: pod/video/templates/videos/add_video.html:51 msgid "Check this box if you want to transcript the audio. (beta version)" msgstr "" -#: pod/recorder/models.py:207 pod/video/models.py:710 +#: pod/recorder/models.py:207 pod/video/models.py:711 msgid "" "Separate tags with spaces, enclose the tags consist of several words in " "quotation marks." msgstr "" -#: pod/recorder/models.py:216 pod/video/forms.py:195 pod/video/models.py:719 +#: pod/recorder/models.py:216 pod/video/forms.py:195 pod/video/models.py:720 msgid "Licence" msgstr "" -#: pod/recorder/models.py:227 pod/video/forms.py:254 pod/video/models.py:539 -#: pod/video/models.py:730 +#: pod/recorder/models.py:227 pod/video/forms.py:254 pod/video/models.py:540 +#: pod/video/models.py:731 msgid "Themes" msgstr "" #: pod/recorder/models.py:230 pod/video/forms.py:189 pod/video/forms.py:262 -#: pod/video/models.py:733 pod/video/models.py:1989 +#: pod/video/models.py:734 pod/video/models.py:2004 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" -#: pod/recorder/models.py:234 pod/video/models.py:737 +#: pod/recorder/models.py:234 pod/video/models.py:738 msgid "allow downloading" msgstr "" -#: pod/recorder/models.py:236 pod/video/models.py:739 +#: pod/recorder/models.py:236 pod/video/models.py:740 msgid "Check this box if you to allow downloading of the encoded files" msgstr "" -#: pod/recorder/models.py:239 pod/video/models.py:742 +#: pod/recorder/models.py:239 pod/video/models.py:743 msgid "video 360" msgstr "" -#: pod/recorder/models.py:241 pod/video/models.py:744 +#: pod/recorder/models.py:241 pod/video/models.py:745 msgid "Check this box if you want to use the 360 player for the video" msgstr "" -#: pod/recorder/models.py:244 pod/video/models.py:803 +#: pod/recorder/models.py:244 pod/video/models.py:804 msgid "Disable comment" msgstr "" -#: pod/recorder/models.py:245 pod/video/models.py:804 +#: pod/recorder/models.py:245 pod/video/models.py:805 msgid "Allows you to turn off all comments on this video." msgstr "" @@ -4109,8 +4646,8 @@ msgstr "" msgid "File size" msgstr "" -#: pod/recorder/models.py:366 pod/video/models.py:685 pod/video/models.py:1812 -#: pod/video/models.py:1869 +#: pod/recorder/models.py:366 pod/video/models.py:686 pod/video/models.py:1827 +#: pod/video/models.py:1884 msgid "Date added" msgstr "" @@ -4221,11 +4758,6 @@ msgstr "" msgid "To delete the record, please checked in and click send." msgstr "" -#: pod/recorder/templates/recorder/record_delete.html:29 -#: pod/video/templates/videos/video_delete.html:30 -msgid "Agree required" -msgstr "" - #: pod/recorder/views.py:93 msgid "Recorder should be indicated." msgstr "" @@ -4287,23 +4819,15 @@ msgstr "" msgid "Pod Administration" msgstr "" -#: pod/video/admin.py:69 +#: pod/video/admin.py:70 msgid "Encoded?" msgstr "" -#: pod/video/admin.py:74 -msgid "Yes" -msgstr "" - -#: pod/video/admin.py:75 -msgid "No" -msgstr "" - -#: pod/video/admin.py:235 +#: pod/video/admin.py:236 msgid "Transcript selected" msgstr "" -#: pod/video/admin.py:323 pod/video/forms.py:371 pod/video/models.py:313 +#: pod/video/admin.py:324 pod/video/forms.py:371 pod/video/models.py:314 msgid "Owners" msgstr "" @@ -4311,7 +4835,7 @@ msgstr "" msgid "File field" msgstr "" -#: pod/video/forms.py:91 pod/video/models.py:630 +#: pod/video/forms.py:91 pod/video/models.py:631 #: pod/video/templates/videos/add_video.html:86 msgid "You can send an audio or video file." msgstr "" @@ -4349,10 +4873,6 @@ msgid "" "list, please temporary select “Other” and contact us to explain your needs." msgstr "" -#: pod/video/forms.py:125 pod/video/models.py:666 -msgid "Additional owners" -msgstr "" - #: pod/video/forms.py:128 msgid "" "In this field you can select and add additional owners to the video. These " @@ -4360,13 +4880,6 @@ msgid "" "delete this video." msgstr "" -#: pod/video/forms.py:138 pod/video/forms.py:355 pod/video/forms.py:406 -#: pod/video/models.py:282 pod/video/models.py:434 pod/video/models.py:680 -msgid "" -"In this field you can describe your content, add all needed related " -"information, and format the result using the toolbar." -msgstr "" - #: pod/video/forms.py:145 msgid "Date of the event field" msgstr "" @@ -4445,11 +4958,11 @@ msgid "" "completion page to improve it." msgstr "" -#: pod/video/forms.py:362 pod/video/models.py:305 +#: pod/video/forms.py:362 pod/video/models.py:306 msgid "Extra style" msgstr "" -#: pod/video/forms.py:362 pod/video/models.py:295 +#: pod/video/forms.py:362 pod/video/models.py:296 msgid "Background color" msgstr "" @@ -4582,346 +5095,336 @@ msgstr "" msgid "In %(deadline)s days" msgstr "" -#: pod/video/models.py:145 +#: pod/video/models.py:146 msgid "Private -" msgstr "" -#: pod/video/models.py:145 +#: pod/video/models.py:146 msgid "Private +" msgstr "" -#: pod/video/models.py:145 +#: pod/video/models.py:146 msgid "Public" msgstr "" -#: pod/video/models.py:157 +#: pod/video/models.py:158 #, python-format msgid "%(app)s version" msgstr "" -#: pod/video/models.py:262 pod/video/models.py:415 +#: pod/video/models.py:263 pod/video/models.py:416 msgid "" "Please choose a title as short and accurate as possible, reflecting the main " "subject / context of the content.(max length: 100 characters)" msgstr "" -#: pod/video/models.py:300 +#: pod/video/models.py:301 msgid "" "The background color for your channel. You can use the format #. i.e.: " "#ff0000 for red" msgstr "" -#: pod/video/models.py:308 +#: pod/video/models.py:309 msgid "The style will be added to your channel to show it" msgstr "" -#: pod/video/models.py:333 +#: pod/video/models.py:334 msgid "" "If checked, the channel appear in a list of available channels on the " "platform." msgstr "" -#: pod/video/models.py:344 +#: pod/video/models.py:345 msgid "Select one or more groups who can upload video to this channel." msgstr "" -#: pod/video/models.py:347 +#: pod/video/models.py:348 msgid "Additionals channels tab" msgstr "" -#: pod/video/models.py:355 pod/video/models.py:449 +#: pod/video/models.py:356 pod/video/models.py:450 #: pod/video_search/templates/search/search.html:120 msgid "Channel" msgstr "" -#: pod/video/models.py:409 +#: pod/video/models.py:410 msgid "Theme parent" msgstr "" -#: pod/video/models.py:538 +#: pod/video/models.py:539 msgid "Theme" msgstr "" -#: pod/video/models.py:560 pod/video/models.py:600 +#: pod/video/models.py:561 pod/video/models.py:601 msgid "Icon" msgstr "" -#: pod/video/models.py:613 pod/video_search/templates/search/search.html:107 +#: pod/video/models.py:614 pod/video_search/templates/search/search.html:107 msgid "Discipline" msgstr "" -#: pod/video/models.py:636 -msgid "" -"Please choose a title as short and accurate as possible, reflecting the main " -"subject / context of the content. (max length: 250 characters)" -msgstr "" - -#: pod/video/models.py:670 +#: pod/video/models.py:671 msgid "" "You can add additional owners to the video. They will have the same rights " "as you except that they can't delete this video." msgstr "" -#: pod/video/models.py:687 -msgid "Date of event" -msgstr "" - -#: pod/video/models.py:783 pod/video/models.py:909 -msgid "Thumbnails" -msgstr "" - -#: pod/video/models.py:787 +#: pod/video/models.py:788 msgid "Overview" msgstr "" -#: pod/video/models.py:798 +#: pod/video/models.py:799 msgid "Is Video" msgstr "" -#: pod/video/models.py:800 +#: pod/video/models.py:801 msgid "Date to delete" msgstr "" -#: pod/video/models.py:814 +#: pod/video/models.py:815 msgid "videos" msgstr "" -#: pod/video/models.py:860 +#: pod/video/models.py:861 msgid "Sum of view" msgstr "" -#: pod/video/models.py:943 +#: pod/video/models.py:868 +msgid "Sum of view of last 6 months (180 days)" +msgstr "" + +#: pod/video/models.py:951 msgid "Is the video encoded?" msgstr "" -#: pod/video/models.py:1279 +#: pod/video/models.py:1294 msgid "Update Owner" msgstr "" -#: pod/video/models.py:1280 +#: pod/video/models.py:1295 msgid "Update Owners" msgstr "" -#: pod/video/models.py:1327 +#: pod/video/models.py:1342 msgid "Date" msgstr "" -#: pod/video/models.py:1328 +#: pod/video/models.py:1343 msgid "Number of view" msgstr "" -#: pod/video/models.py:1336 +#: pod/video/models.py:1351 msgid "View count" msgstr "" -#: pod/video/models.py:1337 +#: pod/video/models.py:1352 msgid "View counts" msgstr "" -#: pod/video/models.py:1342 +#: pod/video/models.py:1357 msgid "resolution" msgstr "" -#: pod/video/models.py:1346 +#: pod/video/models.py:1361 msgid "" "Please use the only format x. i.e.: 640x360 or 1280x720 or " "1920x1080." msgstr "" -#: pod/video/models.py:1352 pod/video/models.py:1443 pod/video/models.py:1450 +#: pod/video/models.py:1367 pod/video/models.py:1458 pod/video/models.py:1465 msgid "minrate" msgstr "" -#: pod/video/models.py:1355 pod/video/models.py:1364 pod/video/models.py:1373 -#: pod/video/models.py:1382 +#: pod/video/models.py:1370 pod/video/models.py:1379 pod/video/models.py:1388 +#: pod/video/models.py:1397 msgid "" "Please use the only format k. i.e.: 300k or 600k or " "1000k." msgstr "" -#: pod/video/models.py:1361 pod/video/models.py:1411 pod/video/models.py:1422 +#: pod/video/models.py:1376 pod/video/models.py:1426 pod/video/models.py:1437 msgid "bitrate video" msgstr "" -#: pod/video/models.py:1370 pod/video/models.py:1431 pod/video/models.py:1438 +#: pod/video/models.py:1385 pod/video/models.py:1446 pod/video/models.py:1453 msgid "maxrate" msgstr "" -#: pod/video/models.py:1379 pod/video/models.py:1468 pod/video/models.py:1479 +#: pod/video/models.py:1394 pod/video/models.py:1483 pod/video/models.py:1494 msgid "bitrate audio" msgstr "" -#: pod/video/models.py:1387 +#: pod/video/models.py:1402 msgid "Make a MP4 version" msgstr "" -#: pod/video/models.py:1400 pod/video/models.py:1505 +#: pod/video/models.py:1415 pod/video/models.py:1520 msgid "rendition" msgstr "" -#: pod/video/models.py:1401 +#: pod/video/models.py:1416 msgid "renditions" msgstr "" -#: pod/video/models.py:1501 pod/video/models.py:1576 pod/video/models.py:1641 +#: pod/video/models.py:1516 pod/video/models.py:1591 pod/video/models.py:1656 msgid "Please use the only format in encoding choices:" msgstr "" -#: pod/video/models.py:1507 pod/video/models.py:1581 pod/video/models.py:1646 +#: pod/video/models.py:1522 pod/video/models.py:1596 pod/video/models.py:1661 msgid "Format" msgstr "" -#: pod/video/models.py:1511 pod/video/models.py:1585 pod/video/models.py:1650 +#: pod/video/models.py:1526 pod/video/models.py:1600 pod/video/models.py:1665 msgid "Please use the only format in format choices:" msgstr "" -#: pod/video/models.py:1515 pod/video/models.py:1589 pod/video/models.py:1654 +#: pod/video/models.py:1530 pod/video/models.py:1604 pod/video/models.py:1669 msgid "encoding source file" msgstr "" -#: pod/video/models.py:1540 +#: pod/video/models.py:1555 msgid "Encoding video" msgstr "" -#: pod/video/models.py:1541 +#: pod/video/models.py:1556 msgid "Encoding videos" msgstr "" -#: pod/video/models.py:1604 +#: pod/video/models.py:1619 msgid "Encoding audio" msgstr "" -#: pod/video/models.py:1605 +#: pod/video/models.py:1620 msgid "Encoding audios" msgstr "" -#: pod/video/models.py:1660 +#: pod/video/models.py:1675 msgid "Video Playlist" msgstr "" -#: pod/video/models.py:1661 +#: pod/video/models.py:1676 msgid "Video Playlists" msgstr "" -#: pod/video/models.py:1715 +#: pod/video/models.py:1730 msgid "Encoding log" msgstr "" -#: pod/video/models.py:1716 +#: pod/video/models.py:1731 msgid "Encoding logs" msgstr "" -#: pod/video/models.py:1727 pod/video/models.py:1736 +#: pod/video/models.py:1742 pod/video/models.py:1751 msgid "Video version" msgstr "" -#: pod/video/models.py:1732 +#: pod/video/models.py:1747 msgid "Video default version." msgstr "" -#: pod/video/models.py:1737 +#: pod/video/models.py:1752 msgid "Video versions" msgstr "" -#: pod/video/models.py:1772 +#: pod/video/models.py:1787 msgid "Encoding steps" msgstr "" -#: pod/video/models.py:1781 pod/video/models.py:1792 pod/video/models.py:1810 +#: pod/video/models.py:1796 pod/video/models.py:1807 pod/video/models.py:1825 #: pod/video/views.py:1647 msgid "Note" msgstr "" -#: pod/video/models.py:1793 +#: pod/video/models.py:1808 msgid "Notes" msgstr "" -#: pod/video/models.py:1804 +#: pod/video/models.py:1819 msgid "Note availability level" msgstr "" -#: pod/video/models.py:1808 +#: pod/video/models.py:1823 msgid "Select an availability level for the note." msgstr "" -#: pod/video/models.py:1811 +#: pod/video/models.py:1826 msgid "Timestamp" msgstr "" -#: pod/video/models.py:1813 pod/video/models.py:1870 +#: pod/video/models.py:1828 pod/video/models.py:1885 msgid "Date modified" msgstr "" -#: pod/video/models.py:1816 +#: pod/video/models.py:1831 msgid "Advanced Note" msgstr "" -#: pod/video/models.py:1817 +#: pod/video/models.py:1832 msgid "Advanced Notes" msgstr "" -#: pod/video/models.py:1862 +#: pod/video/models.py:1877 msgid "Comment availability level" msgstr "" -#: pod/video/models.py:1866 +#: pod/video/models.py:1881 msgid "Select an availability level for the comment." msgstr "" -#: pod/video/models.py:1873 +#: pod/video/models.py:1888 msgid "Note comment" msgstr "" -#: pod/video/models.py:1874 +#: pod/video/models.py:1889 msgid "Note comments" msgstr "" -#: pod/video/models.py:1888 +#: pod/video/models.py:1903 msgid "Date for deletion" msgstr "" -#: pod/video/models.py:1890 pod/video/models.py:1986 +#: pod/video/models.py:1905 pod/video/models.py:2001 #: pod/video/templates/videos/video.html:80 #: pod/video/templates/videos/videos.html:8 #: pod/video/templates/videos/videos.html:17 msgid "Videos" msgstr "" -#: pod/video/models.py:1893 +#: pod/video/models.py:1908 msgid "Video to delete" msgstr "" -#: pod/video/models.py:1894 +#: pod/video/models.py:1909 msgid "Videos to delete" msgstr "" -#: pod/video/models.py:1922 pod/video/templates/videos/video-comment.html:9 +#: pod/video/models.py:1937 pod/video/templates/videos/video-comment.html:9 msgid "Comments" msgstr "" -#: pod/video/models.py:1966 +#: pod/video/models.py:1981 msgid "Vote" msgstr "" -#: pod/video/models.py:1967 +#: pod/video/models.py:1982 msgid "Votes" msgstr "" -#: pod/video/models.py:1975 pod/video/templates/videos/category_modal.html:13 +#: pod/video/models.py:1990 pod/video/templates/videos/category_modal.html:13 msgid "Category title" msgstr "" -#: pod/video/models.py:1978 +#: pod/video/models.py:1993 msgid "" "Please choose a title as short and accurate as possible, reflecting the main " "subject / context of the content.(max length : 100 characters)" msgstr "" -#: pod/video/models.py:2012 pod/video/templates/videos/category_modal.html:7 +#: pod/video/models.py:2027 pod/video/templates/videos/category_modal.html:7 msgid "Category" msgstr "" -#: pod/video/models.py:2013 +#: pod/video/models.py:2028 #: pod/video/templates/videos/filter_aside_category.html:14 msgid "Categories" msgstr "" @@ -5083,11 +5586,6 @@ msgid "" "platform, please" msgstr "" -#: pod/video/templates/videos/add_video.html:31 -#: pod/video/templates/videos/video_edit.html:48 -msgid "contact us" -msgstr "" - #: pod/video/templates/videos/add_video.html:34 msgid "Media upload" msgstr "" @@ -5145,16 +5643,6 @@ msgstr "" msgid "Check the videos to add in the category" msgstr "" -#: pod/video/templates/videos/category_modal.html:30 -#: pod/video/templates/videos/paginator.html:7 -msgid "Previous page" -msgstr "" - -#: pod/video/templates/videos/category_modal.html:35 -#: pod/video/templates/videos/paginator.html:21 -msgid "Next page" -msgstr "" - #: pod/video/templates/videos/category_modal.html:44 msgid "Save category" msgstr "" @@ -5207,11 +5695,6 @@ msgstr "" msgid "Submit changes" msgstr "" -#: pod/video/templates/videos/filter_aside.html:8 -#: pod/video/templates/videos/filter_aside_category.html:6 -msgid "Filters" -msgstr "" - #: pod/video/templates/videos/filter_aside.html:16 msgid "Filter by user" msgstr "" @@ -5339,10 +5822,6 @@ msgstr "" msgid "No information available" msgstr "" -#: pod/video/templates/videos/video-info.html:39 -msgid "Added by:" -msgstr "" - #: pod/video/templates/videos/video-info.html:52 msgid "Additional owner(s):" msgstr "" @@ -5385,10 +5864,6 @@ msgstr "" msgid "Show details views" msgstr "" -#: pod/video/templates/videos/video-info.html:120 -msgid "Type:" -msgstr "" - #: pod/video/templates/videos/video-info.html:125 msgid "Main language:" msgstr "" @@ -5417,15 +5892,6 @@ msgstr "" msgid "Document:" msgstr "" -#: pod/video/templates/videos/video-info.html:199 -#: pod/video/templates/videos/video_edit.html:159 -msgid "Embed/Share (Draft Mode)" -msgstr "" - -#: pod/video/templates/videos/video-info.html:202 -msgid "Social Networks" -msgstr "" - #: pod/video/templates/videos/video-info.html:216 msgid "" "Please note that your video is in draft mode.
      The following links " @@ -5474,26 +5940,6 @@ msgid "" "activity:" msgstr "" -#: pod/video/templates/videos/video-info.html:260 -msgid "Embed in a web page" -msgstr "" - -#: pod/video/templates/videos/video-info.html:262 -msgid "Copy the content of this text box and paste it in the page:" -msgstr "" - -#: pod/video/templates/videos/video-info.html:267 -msgid "Share the link" -msgstr "" - -#: pod/video/templates/videos/video-info.html:269 -msgid "Use this link to share the video:" -msgstr "" - -#: pod/video/templates/videos/video-info.html:273 -msgid "QR code for this link:" -msgstr "" - #: pod/video/templates/videos/video-info.html:274 msgid "Warning, it use google API" msgstr "" @@ -5726,6 +6172,58 @@ msgstr "" msgid "Show view statistics for all videos" msgstr "" +#: pod/video/templatetags/video_tags.py:93 +#, python-format +msgid "tags_for_model tag was given an invalid model: %s" +msgstr "" + +#: pod/video/templatetags/video_tags.py:133 +#, python-format +msgid "%s tag requires either three or five arguments" +msgstr "" + +#: pod/video/templatetags/video_tags.py:136 +#: pod/video/templatetags/video_tags.py:197 +#, python-format +msgid "second argument to %s tag must be 'as'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:140 +#: pod/video/templatetags/video_tags.py:202 +#, python-format +msgid "if given, fourth argument to %s tag must be 'with'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:144 +#, python-format +msgid "if given, fifth argument to %s tag must be 'counts'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:193 +#, python-format +msgid "%s tag requires either three or between five and seven arguments" +msgstr "" + +#: pod/video/templatetags/video_tags.py:217 +#, python-format +msgid "%(tag)s tag was given an invalid option: '%(option)s'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:225 +#, python-format +msgid "%(tag)s tag was given a badly formatted option: '%(option)s'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:243 +#, python-format +msgid "%(tag)s tag's '%(option)s' option was not a valid integer: '%(value)s'" +msgstr "" + +#: pod/video/templatetags/video_tags.py:258 +#, python-format +msgid "%(tag)s tag's '%(option)s' option was not a valid choice: '%(value)s'" +msgstr "" + #: pod/video/tests/test_models.py:222 pod/video/tests/test_models.py:275 #: pod/video/translation.py:12 pod/video/translation.py:17 msgid "-- sorry, no translation provided --" @@ -5736,11 +6234,6 @@ msgstr "" msgid "Transcripting #%(content_id)s completed" msgstr "" -#: pod/video/utils.py:199 pod/video/utils.py:224 pod/video/utils.py:292 -#: pod/video/utils.py:317 -msgid "Hello," -msgstr "" - #: pod/video/utils.py:201 pod/video/utils.py:226 #, python-format msgid "" @@ -5753,16 +6246,6 @@ msgstr "" msgid "You will find it here:" msgstr "" -#: pod/video/utils.py:207 pod/video/utils.py:236 pod/video/utils.py:300 -#: pod/video/utils.py:329 -msgid "Regards." -msgstr "" - -#: pod/video/utils.py:210 pod/video/utils.py:240 pod/video/utils.py:303 -#: pod/video/utils.py:333 -msgid "Post by:" -msgstr "" - #: pod/video/utils.py:212 pod/video/utils.py:242 pod/video/utils.py:305 #: pod/video/utils.py:335 msgid "the:" @@ -5784,10 +6267,6 @@ msgstr "" msgid "You cannot edit this channel." msgstr "" -#: pod/video/views.py:361 pod/video/views.py:947 -msgid "The changes have been saved." -msgstr "" - #: pod/video/views.py:887 msgid "You cannot watch this video." msgstr "" diff --git a/pod/locale/nl/LC_MESSAGES/djangojs.po b/pod/locale/nl/LC_MESSAGES/djangojs.po index 32b3513f54..c55fb440cf 100644 --- a/pod/locale/nl/LC_MESSAGES/djangojs.po +++ b/pod/locale/nl/LC_MESSAGES/djangojs.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: Esup-Pod\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-11-26 10:16+0100\n" +"POT-Creation-Date: 2022-03-25 17:11+0100\n" "PO-Revision-Date: 2021-11-25 16:53+0100\n" "Last-Translator: obado \n" "Language-Team: \n" @@ -20,19 +20,24 @@ msgstr "" #: pod/enrichment/static/js/enrichment.js:9 #: pod/enrichment/static/js/enrichment.js:14 #: pod/enrichment/static/js/enrichment.js:258 -#: pod/enrichment/static/js/enrichment.js:263 +#: pod/enrichment/static/js/enrichment.js:263 pod/static/js/chapters.js:9 +#: pod/static/js/chapters.js:14 pod/static/js/enrichment.js:9 +#: pod/static/js/enrichment.js:14 pod/static/js/enrichment.js:258 +#: pod/static/js/enrichment.js:263 msgid "Get time from the player" msgstr "" #: pod/chapter/static/js/chapters.js:34 #: pod/completion/static/js/completion.js:52 -#: pod/enrichment/static/js/enrichment.js:35 +#: pod/enrichment/static/js/enrichment.js:35 pod/static/js/chapters.js:34 +#: pod/static/js/completion.js:52 pod/static/js/enrichment.js:35 msgid "Error getting form." msgstr "" #: pod/chapter/static/js/chapters.js:38 #: pod/completion/static/js/completion.js:56 -#: pod/enrichment/static/js/enrichment.js:39 +#: pod/enrichment/static/js/enrichment.js:39 pod/static/js/chapters.js:38 +#: pod/static/js/completion.js:56 pod/static/js/enrichment.js:39 msgid "The form could not be recovered." msgstr "" @@ -47,44 +52,60 @@ msgstr "" #: pod/enrichment/static/js/enrichment.js:111 #: pod/enrichment/static/js/enrichment.js:141 #: pod/enrichment/static/js/enrichment.js:174 -#: pod/enrichment/static/js/enrichment.js:215 pod/main/static/js/main.js:468 -#: pod/main/static/js/main.js:478 pod/main/static/js/main.js:492 -#: pod/main/static/js/main.js:512 +#: pod/enrichment/static/js/enrichment.js:215 pod/main/static/js/main.js:479 +#: pod/main/static/js/main.js:489 pod/main/static/js/main.js:503 +#: pod/main/static/js/main.js:523 #: pod/podfile/static/podfile/js/filewidget.js:365 -#: pod/podfile/static/podfile/js/filewidget.js:398 +#: pod/podfile/static/podfile/js/filewidget.js:398 pod/static/js/chapters.js:82 +#: pod/static/js/chapters.js:111 pod/static/js/chapters.js:141 +#: pod/static/js/chapters.js:175 pod/static/js/chapters.js:221 +#: pod/static/js/completion.js:107 pod/static/js/completion.js:149 +#: pod/static/js/completion.js:211 pod/static/js/completion.js:247 +#: pod/static/js/enrichment.js:82 pod/static/js/enrichment.js:111 +#: pod/static/js/enrichment.js:141 pod/static/js/enrichment.js:174 +#: pod/static/js/enrichment.js:215 pod/static/js/main.js:468 +#: pod/static/js/main.js:478 pod/static/js/main.js:492 +#: pod/static/js/main.js:512 pod/static/podfile/js/filewidget.js:365 +#: pod/static/podfile/js/filewidget.js:398 msgid "You are no longer authenticated. Please log in again." msgstr "" #: pod/chapter/static/js/chapters.js:199 -#: pod/enrichment/static/js/enrichment.js:193 pod/main/static/js/main.js:415 -#: pod/main/static/js/main.js:501 +#: pod/enrichment/static/js/enrichment.js:193 pod/main/static/js/main.js:426 +#: pod/main/static/js/main.js:512 pod/static/js/chapters.js:199 +#: pod/static/js/enrichment.js:193 pod/static/js/main.js:415 +#: pod/static/js/main.js:501 msgid "One or more errors have been found in the form." msgstr "" #: pod/chapter/static/js/chapters.js:251 -#: pod/enrichment/static/js/enrichment.js:318 +#: pod/enrichment/static/js/enrichment.js:318 pod/static/js/chapters.js:251 +#: pod/static/js/enrichment.js:318 msgid "Please enter a title from 2 to 100 characters." msgstr "" -#: pod/chapter/static/js/chapters.js:266 +#: pod/chapter/static/js/chapters.js:266 pod/static/js/chapters.js:266 msgid "Please enter a correct start field between 0 and" msgstr "" -#: pod/chapter/static/js/chapters.js:288 +#: pod/chapter/static/js/chapters.js:288 pod/static/js/chapters.js:288 msgid "The chapter" msgstr "" -#: pod/chapter/static/js/chapters.js:292 +#: pod/chapter/static/js/chapters.js:292 pod/static/js/chapters.js:292 msgid "starts at the same time." msgstr "" #: pod/chapter/static/js/videojs-chapters.js:22 #: pod/chapter/static/js/videojs-chapters.js:24 #: pod/chapter/static/js/videojs-chapters.js:26 +#: pod/static/js/videojs-chapters.js:22 pod/static/js/videojs-chapters.js:24 +#: pod/static/js/videojs-chapters.js:26 msgid "Chapters" msgstr "" #: pod/completion/static/js/caption_maker.js:29 +#: pod/static/js/caption_maker.js:29 msgid "" "WEBVTT\n" "\n" @@ -94,370 +115,451 @@ msgstr "" #: pod/completion/static/js/caption_maker.js:48 #: pod/completion/static/js/caption_maker.js:902 +#: pod/static/js/caption_maker.js:48 pod/static/js/caption_maker.js:902 msgid "Unrecognized caption file format." msgstr "" #: pod/completion/static/js/caption_maker.js:59 +#: pod/static/js/caption_maker.js:59 msgid "There is no captions to save" msgstr "" #: pod/completion/static/js/caption_maker.js:94 #: pod/completion/static/js/caption_maker.js:916 +#: pod/static/js/caption_maker.js:94 pod/static/js/caption_maker.js:916 msgid "Not a valid time track file." msgstr "" #: pod/completion/static/js/caption_maker.js:115 +#: pod/static/js/caption_maker.js:115 msgid "error during exchange" msgstr "" #: pod/completion/static/js/caption_maker.js:119 +#: pod/static/js/caption_maker.js:119 msgid "no data could be stored." msgstr "" #: pod/completion/static/js/caption_maker.js:181 +#: pod/static/js/caption_maker.js:181 msgid "Are you sure you want to delete all caption?" msgstr "" #: pod/completion/static/js/caption_maker.js:867 +#: pod/static/js/caption_maker.js:867 msgid "Error reading caption file. Code = " msgstr "" #: pod/completion/static/js/caption_maker.js:874 +#: pod/static/js/caption_maker.js:874 msgid "Exception thrown reading caption file. Code = " msgstr "" #: pod/completion/static/js/caption_maker.js:878 +#: pod/static/js/caption_maker.js:878 msgid "Your browser does not support FileReader." msgstr "" #: pod/completion/static/js/caption_maker.js:885 +#: pod/static/js/caption_maker.js:885 msgid "Error loading caption file: " msgstr "" #: pod/completion/static/js/completion.js:175 #: pod/podfile/static/podfile/js/filewidget.js:277 +#: pod/static/js/completion.js:175 pod/static/podfile/js/filewidget.js:277 msgid "Are you sure you want to delete this file?" msgstr "" -#: pod/completion/static/js/completion.js:179 +#: pod/completion/static/js/completion.js:179 pod/static/js/completion.js:179 msgid "Are you sure you want to delete this contributor?" msgstr "" -#: pod/completion/static/js/completion.js:183 +#: pod/completion/static/js/completion.js:183 pod/static/js/completion.js:183 msgid "Are you sure you want to delete this document?" msgstr "" -#: pod/completion/static/js/completion.js:187 +#: pod/completion/static/js/completion.js:187 pod/static/js/completion.js:187 msgid "Are you sure you want to delete this overlay?" msgstr "" -#: pod/completion/static/js/completion.js:243 pod/main/static/js/main.js:419 +#: pod/completion/static/js/completion.js:243 pod/main/static/js/main.js:430 +#: pod/static/js/completion.js:243 pod/static/js/main.js:419 msgid "Changes have been saved." msgstr "" -#: pod/completion/static/js/completion.js:276 +#: pod/completion/static/js/completion.js:276 pod/static/js/completion.js:276 msgid "Display" msgstr "" -#: pod/completion/static/js/completion.js:276 +#: pod/completion/static/js/completion.js:276 pod/static/js/completion.js:276 msgid "section" msgstr "" -#: pod/completion/static/js/completion.js:304 +#: pod/completion/static/js/completion.js:304 pod/static/js/completion.js:304 msgid "Please enter a name from 2 to 100 caracteres." msgstr "" -#: pod/completion/static/js/completion.js:316 +#: pod/completion/static/js/completion.js:316 pod/static/js/completion.js:316 msgid "You cannot enter a weblink with more than 200 caracteres." msgstr "" -#: pod/completion/static/js/completion.js:328 +#: pod/completion/static/js/completion.js:328 pod/static/js/completion.js:328 msgid "Please enter a role." msgstr "" -#: pod/completion/static/js/completion.js:345 +#: pod/completion/static/js/completion.js:345 pod/static/js/completion.js:345 msgid "" "There is already a contributor with this same name and role in the list." msgstr "" -#: pod/completion/static/js/completion.js:361 +#: pod/completion/static/js/completion.js:361 pod/static/js/completion.js:361 msgid "Please enter a correct kind." msgstr "" -#: pod/completion/static/js/completion.js:376 +#: pod/completion/static/js/completion.js:376 pod/static/js/completion.js:376 msgid "Please select a language." msgstr "" -#: pod/completion/static/js/completion.js:391 +#: pod/completion/static/js/completion.js:391 pod/static/js/completion.js:391 msgid "Please specify a track file." msgstr "" -#: pod/completion/static/js/completion.js:401 +#: pod/completion/static/js/completion.js:401 pod/static/js/completion.js:401 msgid "Only .vtt format is allowed." msgstr "" -#: pod/completion/static/js/completion.js:436 +#: pod/completion/static/js/completion.js:436 pod/static/js/completion.js:436 msgid "There is already a track with the same kind and language in the list." msgstr "" #: pod/completion/static/js/completion.js:452 -#: pod/enrichment/static/js/enrichment.js:422 +#: pod/enrichment/static/js/enrichment.js:422 pod/static/js/completion.js:452 +#: pod/static/js/enrichment.js:422 msgid "Please select a document." msgstr "" -#: pod/completion/static/js/completion.js:465 +#: pod/completion/static/js/completion.js:465 pod/static/js/completion.js:465 msgid "Iframe and Script tags are not allowed." msgstr "" -#: pod/enrichment/static/js/enrichment.js:333 +#: pod/enrichment/static/js/enrichment.js:333 pod/static/js/enrichment.js:333 msgid "Please enter a correct start from 0 to " msgstr "" -#: pod/enrichment/static/js/enrichment.js:349 +#: pod/enrichment/static/js/enrichment.js:349 pod/static/js/enrichment.js:349 msgid "Please enter a correct end from 1 to " msgstr "" -#: pod/enrichment/static/js/enrichment.js:367 +#: pod/enrichment/static/js/enrichment.js:367 pod/static/js/enrichment.js:367 msgid "Please enter a correct image." msgstr "" -#: pod/enrichment/static/js/enrichment.js:380 +#: pod/enrichment/static/js/enrichment.js:380 pod/static/js/enrichment.js:380 msgid "Please enter a correct richtext." msgstr "" -#: pod/enrichment/static/js/enrichment.js:393 +#: pod/enrichment/static/js/enrichment.js:393 pod/static/js/enrichment.js:393 msgid "Please enter a correct weblink." msgstr "" -#: pod/enrichment/static/js/enrichment.js:404 +#: pod/enrichment/static/js/enrichment.js:404 pod/static/js/enrichment.js:404 msgid "Weblink must be less than 200 characters." msgstr "" -#: pod/enrichment/static/js/enrichment.js:435 +#: pod/enrichment/static/js/enrichment.js:435 pod/static/js/enrichment.js:435 msgid "Please enter a correct embed." msgstr "" -#: pod/enrichment/static/js/enrichment.js:446 +#: pod/enrichment/static/js/enrichment.js:446 pod/static/js/enrichment.js:446 msgid "Embed field must be less than 300 characters." msgstr "" -#: pod/enrichment/static/js/enrichment.js:459 +#: pod/enrichment/static/js/enrichment.js:459 pod/static/js/enrichment.js:459 msgid "Please enter a type in index field." msgstr "" -#: pod/enrichment/static/js/enrichment.js:474 +#: pod/enrichment/static/js/enrichment.js:474 pod/static/js/enrichment.js:474 msgid "The start field value is greater than the end field one." msgstr "" -#: pod/enrichment/static/js/enrichment.js:476 +#: pod/enrichment/static/js/enrichment.js:476 pod/static/js/enrichment.js:476 msgid "The end field value is greater than the video duration." msgstr "" -#: pod/enrichment/static/js/enrichment.js:478 +#: pod/enrichment/static/js/enrichment.js:478 pod/static/js/enrichment.js:478 msgid "End field and start field cannot be equal." msgstr "" -#: pod/enrichment/static/js/enrichment.js:503 +#: pod/enrichment/static/js/enrichment.js:503 pod/static/js/enrichment.js:503 msgid "There is an overlap with the enrichment " msgstr "" -#: pod/enrichment/static/js/enrichment.js:507 +#: pod/enrichment/static/js/enrichment.js:507 pod/static/js/enrichment.js:507 msgid "please change start and/or end values." msgstr "" +#: pod/live/static/js/broadcaster_from_building.js:12 +#: pod/static/js/broadcaster_from_building.js:12 +msgid "Restricted because the broadcaster is restricted" +msgstr "" + +#: pod/live/static/js/broadcaster_from_building.js:16 +#: pod/static/js/broadcaster_from_building.js:16 +msgid "" +"If this box is checked, the event will only be accessible to authenticated " +"users." +msgstr "" + +#: pod/live/static/js/broadcaster_from_building.js:39 +#: pod/static/js/broadcaster_from_building.js:39 +msgid "an error occurred on broadcaster fetch ..." +msgstr "" + +#: pod/live/static/js/broadcaster_from_building.js:62 +#: pod/static/js/broadcaster_from_building.js:62 +msgid "No broadcaster set for this building" +msgstr "" + +#: pod/live/static/js/broadcaster_from_building.js:77 +#: pod/static/js/broadcaster_from_building.js:77 +msgid "an error occurred during broadcasters load ..." +msgstr "" + #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gView" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gBox" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "overlay" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "loading" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "states.hover" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "states.disabled" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "pager.pagerSelect" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "pager.pagerInput" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "pager.pagerButton" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "states.hoverTh" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "colHeaders" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "hTable" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "states.select" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "resizer" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gridFooter" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "rowFooter" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gridRow" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "grid" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "top" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "bottom" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "hDiv" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "pager.pager" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "titleButton" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gridTitle" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "toolbarUpper" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "toolbarBottom" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "rowNum" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gridError" msgstr "" #: pod/main/static/js/jquery.jqgrid.min.js:10 +#: pod/static/js/jquery.jqgrid.min.js:10 msgid "gridErrorText" msgstr "" -#: pod/main/static/js/main.js:378 +#: pod/main/static/js/main.js:389 pod/static/js/main.js:378 msgid "Are you sure you want to delete this element?" msgstr "" -#: pod/main/static/js/main.js:454 +#: pod/main/static/js/main.js:465 #: pod/podfile/static/podfile/js/filewidget.js:114 -#: pod/podfile/static/podfile/js/filewidget.js:391 +#: pod/podfile/static/podfile/js/filewidget.js:391 pod/static/js/main.js:454 +#: pod/static/podfile/js/filewidget.js:114 +#: pod/static/podfile/js/filewidget.js:391 msgid "Error during exchange" msgstr "" -#: pod/main/static/js/main.js:458 -#: pod/podfile/static/podfile/js/filewidget.js:114 +#: pod/main/static/js/main.js:469 +#: pod/podfile/static/podfile/js/filewidget.js:114 pod/static/js/main.js:458 +#: pod/static/podfile/js/filewidget.js:114 msgid "No data could be stored." msgstr "" -#: pod/main/static/js/main.js:531 +#: pod/main/static/js/main.js:542 pod/static/js/main.js:531 msgid "Change your picture" msgstr "" -#: pod/main/static/js/main.js:538 +#: pod/main/static/js/main.js:549 pod/static/js/main.js:538 msgid "Add your picture" msgstr "" -#: pod/main/static/js/main.js:666 +#: pod/main/static/js/main.js:677 pod/static/js/main.js:666 msgid "text copied" msgstr "" -#: pod/main/static/js/main.js:718 +#: pod/main/static/js/main.js:729 pod/static/js/main.js:718 msgid "Errors appear in the form, please correct them" msgstr "" -#: pod/main/static/js/main.js:750 +#: pod/main/static/js/main.js:761 pod/static/js/main.js:750 msgid "The file size exceeds the maximum allowed value:" msgstr "" -#: pod/main/static/js/main.js:753 +#: pod/main/static/js/main.js:764 pod/static/js/main.js:753 msgid " GB." msgstr "" -#: pod/main/static/js/main.js:771 +#: pod/main/static/js/main.js:782 pod/static/js/main.js:771 msgid "The file extension not in the allowed extension:" msgstr "" -#: pod/playlist/static/js/playlist.js:172 +#: pod/playlist/static/js/playlist.js:172 pod/static/js/playlist.js:172 msgid "The video can not be added from this page." msgstr "" -#: pod/playlist/static/js/playlist.js:179 +#: pod/playlist/static/js/playlist.js:178 pod/static/js/playlist.js:178 msgid "The video slug not found." msgstr "" #: pod/podfile/static/podfile/js/filewidget.js:11 +#: pod/static/podfile/js/filewidget.js:11 msgid "Change image" msgstr "" #: pod/podfile/static/podfile/js/filewidget.js:13 +#: pod/static/podfile/js/filewidget.js:13 msgid "Change file" msgstr "" #: pod/podfile/static/podfile/js/filewidget.js:28 +#: pod/static/podfile/js/filewidget.js:28 msgid "Open file in a new tab" msgstr "" #: pod/podfile/static/podfile/js/filewidget.js:55 +#: pod/static/podfile/js/filewidget.js:55 msgid "This folder is empty" msgstr "" #: pod/podfile/static/podfile/js/filewidget.js:134 #: pod/podfile/static/podfile/js/filewidget.js:141 +#: pod/static/podfile/js/filewidget.js:134 +#: pod/static/podfile/js/filewidget.js:141 msgid "Change" msgstr "" #: pod/podfile/static/podfile/js/filewidget.js:149 +#: pod/static/podfile/js/filewidget.js:149 msgid "Enter new name of folder" msgstr "" #: pod/podfile/static/podfile/js/filewidget.js:167 +#: pod/static/admin/js/SelectFilter2.js:86 +#: pod/static/podfile/js/filewidget.js:167 msgid "Remove" msgstr "" @@ -465,50 +567,298 @@ msgstr "" #: pod/podfile/static/podfile/js/filewidget.js:202 #: pod/podfile/static/podfile/js/filewidget.js:235 #: pod/podfile/static/podfile/js/filewidget.js:254 +#: pod/static/podfile/js/filewidget.js:180 +#: pod/static/podfile/js/filewidget.js:202 +#: pod/static/podfile/js/filewidget.js:235 +#: pod/static/podfile/js/filewidget.js:254 msgid "Server error" msgstr "" #: pod/podfile/static/podfile/js/filewidget.js:188 +#: pod/static/podfile/js/filewidget.js:188 msgid "Add" msgstr "" #: pod/podfile/static/podfile/js/filewidget.js:268 +#: pod/static/podfile/js/filewidget.js:268 msgid "Are you sure you want to delete this folder?" msgstr "" #: pod/podfile/static/podfile/js/filewidget.js:477 +#: pod/static/podfile/js/filewidget.js:477 msgid "See more" msgstr "" +#: pod/static/admin/js/SelectFilter2.js:47 +#, javascript-format +msgid "Available %s" +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:53 +#, javascript-format +msgid "" +"This is the list of available %s. You may choose some by selecting them in " +"the box below and then clicking the \"Choose\" arrow between the two boxes." +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:69 +#, javascript-format +msgid "Type into this box to filter down the list of available %s." +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:74 +msgid "Filter" +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:78 +msgid "Choose all" +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:78 +#, javascript-format +msgid "Click to choose all %s at once." +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:84 +msgid "Choose" +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:92 +#, javascript-format +msgid "Chosen %s" +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:98 +#, javascript-format +msgid "" +"This is the list of chosen %s. You may remove some by selecting them in the " +"box below and then clicking the \"Remove\" arrow between the two boxes." +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:108 +msgid "Remove all" +msgstr "" + +#: pod/static/admin/js/SelectFilter2.js:108 +#, javascript-format +msgid "Click to remove all chosen %s at once." +msgstr "" + +#: pod/static/admin/js/actions.js:48 pod/static/admin/js/actions.min.js:2 +msgid "%(sel)s of %(cnt)s selected" +msgid_plural "%(sel)s of %(cnt)s selected" +msgstr[0] "" +msgstr[1] "" + +#: pod/static/admin/js/actions.js:117 pod/static/admin/js/actions.min.js:4 +msgid "" +"You have unsaved changes on individual editable fields. If you run an " +"action, your unsaved changes will be lost." +msgstr "" + +#: pod/static/admin/js/actions.js:129 pod/static/admin/js/actions.min.js:5 +msgid "" +"You have selected an action, but you haven't saved your changes to " +"individual fields yet. Please click OK to save. You'll need to re-run the " +"action." +msgstr "" + +#: pod/static/admin/js/actions.js:131 pod/static/admin/js/actions.min.js:5 +msgid "" +"You have selected an action, and you haven't made any changes on individual " +"fields. You're probably looking for the Go button rather than the Save " +"button." +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:74 +#, javascript-format +msgid "Note: You are %s hour ahead of server time." +msgid_plural "Note: You are %s hours ahead of server time." +msgstr[0] "" +msgstr[1] "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:82 +#, javascript-format +msgid "Note: You are %s hour behind server time." +msgid_plural "Note: You are %s hours behind server time." +msgstr[0] "" +msgstr[1] "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:109 +#: pod/static/admin/js/admin/DateTimeShortcuts.js:160 +msgid "Now" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:127 +msgid "Choose a Time" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:157 +msgid "Choose a time" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:165 +msgid "Midnight" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:170 +msgid "6 a.m." +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:175 +msgid "Noon" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:180 +msgid "6 p.m." +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:188 +#: pod/static/admin/js/admin/DateTimeShortcuts.js:348 +#: pod/static/js/comment-script.js:98 pod/video/static/js/comment-script.js:98 +msgid "Cancel" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:253 +#: pod/static/admin/js/admin/DateTimeShortcuts.js:333 +msgid "Today" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:270 +msgid "Choose a Date" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:327 +msgid "Yesterday" +msgstr "" + +#: pod/static/admin/js/admin/DateTimeShortcuts.js:339 +msgid "Tomorrow" +msgstr "" + +#: pod/static/admin/js/calendar.js:12 +msgid "January" +msgstr "" + +#: pod/static/admin/js/calendar.js:13 +msgid "February" +msgstr "" + +#: pod/static/admin/js/calendar.js:14 +msgid "March" +msgstr "" + +#: pod/static/admin/js/calendar.js:15 +msgid "April" +msgstr "" + +#: pod/static/admin/js/calendar.js:16 +msgid "May" +msgstr "" + +#: pod/static/admin/js/calendar.js:17 +msgid "June" +msgstr "" + +#: pod/static/admin/js/calendar.js:18 +msgid "July" +msgstr "" + +#: pod/static/admin/js/calendar.js:19 +msgid "August" +msgstr "" + +#: pod/static/admin/js/calendar.js:20 +msgid "September" +msgstr "" + +#: pod/static/admin/js/calendar.js:21 +msgid "October" +msgstr "" + +#: pod/static/admin/js/calendar.js:22 +msgid "November" +msgstr "" + +#: pod/static/admin/js/calendar.js:23 +msgid "December" +msgstr "" + +#: pod/static/admin/js/calendar.js:26 +msgctxt "one letter Sunday" +msgid "S" +msgstr "" + +#: pod/static/admin/js/calendar.js:27 +msgctxt "one letter Monday" +msgid "M" +msgstr "" + +#: pod/static/admin/js/calendar.js:28 +msgctxt "one letter Tuesday" +msgid "T" +msgstr "" + +#: pod/static/admin/js/calendar.js:29 +msgctxt "one letter Wednesday" +msgid "W" +msgstr "" + +#: pod/static/admin/js/calendar.js:30 +msgctxt "one letter Thursday" +msgid "T" +msgstr "" + +#: pod/static/admin/js/calendar.js:31 +msgctxt "one letter Friday" +msgid "F" +msgstr "" + +#: pod/static/admin/js/calendar.js:32 +msgctxt "one letter Saturday" +msgid "S" +msgstr "" + +#: pod/static/admin/js/collapse.js:10 pod/static/admin/js/collapse.js:21 +#: pod/static/admin/js/collapse.min.js:1 +msgid "Show" +msgstr "" + +#: pod/static/admin/js/collapse.js:18 pod/static/admin/js/collapse.min.js:1 +msgid "Hide" +msgstr "" + +#: pod/static/js/change_video_owner.js:104 #: pod/video/static/js/change_video_owner.js:104 msgid "No element found" msgstr "" +#: pod/static/js/change_video_owner.js:637 #: pod/video/static/js/change_video_owner.js:637 msgid "An error occurred during the change of owner" msgstr "" +#: pod/static/js/change_video_owner.js:648 #: pod/video/static/js/change_video_owner.js:648 msgid "Please complete all fields correctly" msgstr "" -#: pod/video/static/js/comment-script.js:29 +#: pod/static/js/comment-script.js:29 pod/video/static/js/comment-script.js:29 msgid "Answer" msgstr "" -#: pod/video/static/js/comment-script.js:30 +#: pod/static/js/comment-script.js:30 pod/video/static/js/comment-script.js:30 msgid "Answers" msgstr "" +#: pod/static/js/comment-script.js:85 pod/static/js/comment-script.js:239 #: pod/video/static/js/comment-script.js:85 #: pod/video/static/js/comment-script.js:239 msgid "Delete" msgstr "" -#: pod/video/static/js/comment-script.js:98 -msgid "Cancel" -msgstr "" - +#: pod/static/js/comment-script.js:177 pod/static/js/comment-script.js:447 +#: pod/static/js/comment-script.js:464 #: pod/video/static/js/comment-script.js:177 #: pod/video/static/js/comment-script.js:447 #: pod/video/static/js/comment-script.js:464 @@ -518,169 +868,216 @@ msgid_plural "%s votes" msgstr[0] "" msgstr[1] "" +#: pod/static/js/comment-script.js:188 #: pod/video/static/js/comment-script.js:188 msgid "Agree with the comment" msgstr "" +#: pod/static/js/comment-script.js:215 #: pod/video/static/js/comment-script.js:215 msgid "Reply to comment" msgstr "" +#: pod/static/js/comment-script.js:218 #: pod/video/static/js/comment-script.js:218 msgid "Reply" msgstr "" +#: pod/static/js/comment-script.js:236 #: pod/video/static/js/comment-script.js:236 msgid "Remove this comment" msgstr "" +#: pod/static/js/comment-script.js:259 #: pod/video/static/js/comment-script.js:259 msgid "Add a public comment" msgstr "" +#: pod/static/js/comment-script.js:383 #: pod/video/static/js/comment-script.js:383 msgid "Show answers" msgstr "" +#: pod/static/js/comment-script.js:602 #: pod/video/static/js/comment-script.js:602 msgid "Comment has been deleted successfully." msgstr "" +#: pod/static/js/filter_aside_video_list_refresh.js:58 +#: pod/video/static/js/filter_aside_video_list_refresh.js:60 +msgid "An Error occurred while processing " +msgstr "" + +#: pod/static/js/regroup_videos_by_theme.js:51 +#: pod/static/js/video_category.js:530 #: pod/video/static/js/regroup_videos_by_theme.js:51 #: pod/video/static/js/video_category.js:530 msgid "This content is password protected." msgstr "" +#: pod/static/js/regroup_videos_by_theme.js:59 +#: pod/static/js/video_category.js:538 #: pod/video/static/js/regroup_videos_by_theme.js:59 #: pod/video/static/js/video_category.js:538 msgid "This content is chaptered." msgstr "" +#: pod/static/js/regroup_videos_by_theme.js:67 +#: pod/static/js/video_category.js:546 #: pod/video/static/js/regroup_videos_by_theme.js:67 #: pod/video/static/js/video_category.js:546 msgid "This content is in draft." msgstr "" +#: pod/static/js/regroup_videos_by_theme.js:76 +#: pod/static/js/video_category.js:436 pod/static/js/video_category.js:555 #: pod/video/static/js/regroup_videos_by_theme.js:76 #: pod/video/static/js/video_category.js:436 #: pod/video/static/js/video_category.js:555 msgid "Video content." msgstr "" +#: pod/static/js/regroup_videos_by_theme.js:81 +#: pod/static/js/video_category.js:437 pod/static/js/video_category.js:560 #: pod/video/static/js/regroup_videos_by_theme.js:81 #: pod/video/static/js/video_category.js:437 #: pod/video/static/js/video_category.js:560 msgid "Audio content." msgstr "" +#: pod/static/js/regroup_videos_by_theme.js:86 +#: pod/static/js/video_category.js:565 #: pod/video/static/js/regroup_videos_by_theme.js:86 #: pod/video/static/js/video_category.js:565 msgid "Edit the video" msgstr "" +#: pod/static/js/regroup_videos_by_theme.js:87 +#: pod/static/js/video_category.js:566 #: pod/video/static/js/regroup_videos_by_theme.js:87 #: pod/video/static/js/video_category.js:566 msgid "Complete the video" msgstr "" +#: pod/static/js/regroup_videos_by_theme.js:88 +#: pod/static/js/video_category.js:567 #: pod/video/static/js/regroup_videos_by_theme.js:88 #: pod/video/static/js/video_category.js:567 msgid "Chapter the video" msgstr "" +#: pod/static/js/regroup_videos_by_theme.js:89 +#: pod/static/js/video_category.js:568 #: pod/video/static/js/regroup_videos_by_theme.js:89 #: pod/video/static/js/video_category.js:568 msgid "Delete the video" msgstr "" +#: pod/static/js/regroup_videos_by_theme.js:270 #: pod/video/static/js/regroup_videos_by_theme.js:270 msgid "Loading videos.." msgstr "" +#: pod/static/js/validate-date_delete-field.js:32 #: pod/video/static/js/validate-date_delete-field.js:32 msgid "The date must be before or equal to" msgstr "" -#: pod/video/static/js/video_category.js:27 +#: pod/static/js/video_category.js:27 pod/video/static/js/video_category.js:27 msgid "Category changes saved successfully" msgstr "" -#: pod/video/static/js/video_category.js:29 +#: pod/static/js/video_category.js:29 pod/video/static/js/video_category.js:29 msgid "You cannot add two categories with the same title." msgstr "" -#: pod/video/static/js/video_category.js:31 +#: pod/static/js/video_category.js:31 pod/video/static/js/video_category.js:31 msgid "Category deleted successfully" msgstr "" -#: pod/video/static/js/video_category.js:33 +#: pod/static/js/video_category.js:33 pod/video/static/js/video_category.js:33 msgid "An error occured, please refresh the page and try again." msgstr "" -#: pod/video/static/js/video_category.js:35 +#: pod/static/js/video_category.js:35 pod/video/static/js/video_category.js:35 msgid "Category title field is required." msgstr "" -#: pod/video/static/js/video_category.js:36 +#: pod/static/js/video_category.js:36 pod/video/static/js/video_category.js:36 msgid "Save category" msgstr "" +#: pod/static/js/video_category.js:187 #: pod/video/static/js/video_category.js:187 msgid "videos found" msgstr "" +#: pod/static/js/video_category.js:187 #: pod/video/static/js/video_category.js:187 msgid "video found" msgstr "" +#: pod/static/js/video_category.js:191 #: pod/video/static/js/video_category.js:191 msgid "Sorry, no video found" msgstr "" +#: pod/static/js/video_category.js:455 #: pod/video/static/js/video_category.js:455 msgid "Edit the category" msgstr "" +#: pod/static/js/video_category.js:468 #: pod/video/static/js/video_category.js:468 msgid "Delete the category" msgstr "" +#: pod/static/js/video_category.js:636 #: pod/video/static/js/video_category.js:636 msgid "Success.." msgstr "" +#: pod/static/js/video_category.js:637 #: pod/video/static/js/video_category.js:637 msgid "Error.." msgstr "" +#: pod/static/js/video_category.js:907 #: pod/video/static/js/video_category.js:907 msgid "Category created successfully" msgstr "" +#: pod/static/js/video_category.js:927 #: pod/video/static/js/video_category.js:927 msgid "Create category" msgstr "" +#: pod/static/js/video_stats_view.js:19 #: pod/video/static/js/video_stats_view.js:19 msgid "Title" msgstr "" +#: pod/static/js/video_stats_view.js:20 #: pod/video/static/js/video_stats_view.js:20 msgid "View during the day" msgstr "" +#: pod/static/js/video_stats_view.js:21 #: pod/video/static/js/video_stats_view.js:21 msgid "View during the month" msgstr "" +#: pod/static/js/video_stats_view.js:22 #: pod/video/static/js/video_stats_view.js:22 msgid "View during the year" msgstr "" +#: pod/static/js/video_stats_view.js:23 #: pod/video/static/js/video_stats_view.js:23 msgid "Total view from creation" msgstr "" +#: pod/static/js/video_stats_view.js:24 #: pod/video/static/js/video_stats_view.js:24 msgid "Slug" msgstr "" diff --git a/pod/main/context_processors.py b/pod/main/context_processors.py index f1b5113e54..c7076b9459 100644 --- a/pod/main/context_processors.py +++ b/pod/main/context_processors.py @@ -97,6 +97,12 @@ COOKIE_LEARN_MORE = getattr(django_settings, "COOKIE_LEARN_MORE", "") +SHOW_EVENTS_ON_HOMEPAGE = getattr(django_settings, "SHOW_EVENTS_ON_HOMEPAGE", False) + +DEFAULT_EVENT_THUMBNAIL = getattr( + django_settings, "DEFAULT_EVENT_THUMBNAIL", "/img/default-event.svg" +) + USE_OPENCAST_STUDIO = getattr(django_settings, "USE_OPENCAST_STUDIO", False) @@ -160,7 +166,8 @@ def context_settings(request): new_settings["DYSLEXIAMODE_ENABLED"] = DYSLEXIAMODE_ENABLED new_settings["USE_OPENCAST_STUDIO"] = USE_OPENCAST_STUDIO new_settings["COOKIE_LEARN_MORE"] = COOKIE_LEARN_MORE - + new_settings["SHOW_EVENTS_ON_HOMEPAGE"] = SHOW_EVENTS_ON_HOMEPAGE + new_settings["DEFAULT_EVENT_THUMBNAIL"] = DEFAULT_EVENT_THUMBNAIL return new_settings diff --git a/pod/main/settings.py b/pod/main/settings.py index c470a4cb42..c843a98ddd 100644 --- a/pod/main/settings.py +++ b/pod/main/settings.py @@ -284,6 +284,13 @@ # ie : USE_THEME = 'green' +SHOW_EVENTS_ON_HOMEPAGE = False +BROADCASTER_PILOTING_SOFTWARE = [ + "Wowza", +] +DEFAULT_EVENT_PATH = "" +DEFAULT_EVENT_THUMBNAIL = "/img/default-event.svg" +DEFAULT_EVENT_TYPE_ID = 1 """ ## # Main menu settings: diff --git a/pod/main/static/css/iframe.css b/pod/main/static/css/iframe.css index ee43001122..002168c60d 100644 --- a/pod/main/static/css/iframe.css +++ b/pod/main/static/css/iframe.css @@ -9,6 +9,23 @@ nav { #mainbreadcrumb { display:none !important; } +#livename { + display:none !important; +} +.container-pod { + padding: 0 !important; + margin: 0 !important; +} +.mt-3 { + padding: 0 !important; + margin: 0 !important; +} +.back-to-top { + display:none !important; +} +#event_add { + display:none !important; +} #navbar_collapse { display:none !important; } diff --git a/pod/main/templates/base.html b/pod/main/templates/base.html index a61e8c1700..70a0b64c41 100644 --- a/pod/main/templates/base.html +++ b/pod/main/templates/base.html @@ -38,6 +38,9 @@ {% if CSS_OVERRIDE %} {% endif %} + {% if SHOW_EVENTS_ON_HOMEPAGE and "live" in THIRD_PARTY_APPS %} + + {% endif %} {% block more_style %}{% endblock more_style %} @@ -104,7 +107,10 @@

      {{page_title|capfirst}}

      {% if request.path == "/" %} - {% include "videos/last_videos.html" %} + {% if SHOW_EVENTS_ON_HOMEPAGE and "live" in THIRD_PARTY_APPS %} + {% include "live/events_next.html" %} + {% endif %} + {% include "videos/last_videos.html" %} {% endif %}