diff --git a/.coveragerc b/.coveragerc index 1e4b063304..f3ec4743f8 100755 --- a/.coveragerc +++ b/.coveragerc @@ -1,12 +1,19 @@ [report] +# Add a column to the report with a summary of which lines (and branches) the tests missed. +# This makes it easy to go from a failure to fixing it, rather than using the HTML report. +show_missing = True -# nb: you can also add a "# pragma: no cover" -# on each function you don't want to be covered [run] relative_files = True source = . + +# This ensures that your code runs through both the True and False paths of each conditional statement. +# branch = True + # Here you can exclude a file from coverage testing +# nb: you can also add a "# pragma: no cover" +# on each function you don't want to be covered omit = pod/*settings*.py pod/custom/settings*.py pod/*apps.py diff --git a/.eslintrc.js b/.eslintrc.js index cb0dc50957..cc8bbe144a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,6 +30,8 @@ module.exports = { }, /* functions and Objects that will not trigger a "not defined" error. */ "globals": { + "require": true, + "process": true, "Cookies": "readonly", "gettext": "readonly", "ngettext": "readonly", @@ -41,5 +43,12 @@ module.exports = { "showalert": "writable", "showLoader": "writable", "manageDisableBtn": "writable" - } + }, + overrides: [ + { + // Allow use of import & export functions + files: [ "pod/main/static/js/utils.js", "pod/video/static/js/regroup_videos_by_theme.js" ], + parserOptions: { sourceType: "module" }, + } + ] }; diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index f8b37c1356..2a24b508e3 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -1,6 +1,5 @@ --- name: Code Formatting -run-name: ${{ github.actor }} is running Pod code formatting 🎨 on: push: diff --git a/.github/workflows/pod_dev.yml b/.github/workflows/pod_dev.yml index 519d71cee9..673d71af5b 100644 --- a/.github/workflows/pod_dev.yml +++ b/.github/workflows/pod_dev.yml @@ -56,18 +56,17 @@ jobs: - name: Install Dependencies run: | - sudo apt-get clean sudo apt-get update - sudo apt-get install ffmpeg - sudo apt-get install -y ffmpegthumbnailer + sudo apt-get install -y --no-install-recommends ffmpeg ffmpegthumbnailer + sudo apt-get clean + sudo rm -rf /var/lib/apt/lists/* python -m pip install --upgrade pip pip install -r requirements-dev.txt sudo npm install -g yarn - # FLAKE 8 + # FLAKE 8 (see setup.cfg for configurations) - name: Flake8 compliance - run: | - flake8 --max-complexity=7 --ignore=E501,W503,E203 --exclude .git,pod/*/migrations/*.py,test_settings.py,node_modules/*/*.py,pod/static/*.py,pod/custom/tenants/*/*.py + run: flake8 ## Start remote encoding and transcoding test ## - name: Create settings local file @@ -147,6 +146,12 @@ jobs: coverage run --append manage.py test -v 3 --settings=pod.main.test_settings coverage xml -o cobertura.xml + - name: Codacy coverage reporter + run: bash <(curl -Ls https://coverage.codacy.com/get.sh) + continue-on-error: true + env: + CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} + - name: Send coverage to coveralls.io # coveralls command has been installed by requirements-dev.txt env: diff --git a/.github/workflows/pod_main.yml b/.github/workflows/pod_main.yml index 6b85e3942f..b1ab94202c 100644 --- a/.github/workflows/pod_main.yml +++ b/.github/workflows/pod_main.yml @@ -45,11 +45,10 @@ jobs: - name: Install Dependencies run: | - sudo apt-get clean sudo apt-get update - sudo apt-get install ffmpeg - sudo apt-get install -y ffmpegthumbnailer - sudo apt-get install -y npm + sudo apt-get install -y --no-install-recommends ffmpeg ffmpegthumbnailer npm + sudo apt-get clean + sudo rm -rf /var/lib/apt/lists/* python -m pip install --upgrade pip pip install -r requirements-dev.txt sudo npm install -g yarn diff --git a/.gitignore b/.gitignore index 28bf70b800..bb869c0a8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# Compiled source # +# Compiled source # ################### *.com *.class @@ -60,6 +60,7 @@ chromedriver pod/static/ *.bak .coverage +.cache_ggshield htmlcov compile-model diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..68b5d5639c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: + - repo: https://github.com/gitguardian/ggshield + rev: v1.27.0 + hooks: + - id: ggshield + language_version: python3 + stages: [commit] diff --git a/Makefile b/Makefile index c542406ed0..fe88dce444 100755 --- a/Makefile +++ b/Makefile @@ -67,7 +67,7 @@ migrate: # Launch all unit tests. tests: - coverage run --source='.' manage.py test --settings=pod.main.test_settings + coverage run manage.py test --settings=pod.main.test_settings coverage html # Ensure coherence of all code style diff --git a/dockerfile-dev-with-volumes/pod-back/Dockerfile b/dockerfile-dev-with-volumes/pod-back/Dockerfile index 99aed8134c..d092993fc2 100755 --- a/dockerfile-dev-with-volumes/pod-back/Dockerfile +++ b/dockerfile-dev-with-volumes/pod-back/Dockerfile @@ -19,7 +19,10 @@ FROM $PYTHON_VERSION # TODO #FROM harbor.urba.univ-lille.fr/store/python:3.7-buster -RUN apt-get clean && apt-get update && apt-get install -y netcat && apt-get install -y gettext +RUN apt-get update \ + && apt-get install -y --no-install-recommends netcat gettext \ + && apt-get clean\ + && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app diff --git a/dockerfile-dev-with-volumes/pod-encode/Dockerfile b/dockerfile-dev-with-volumes/pod-encode/Dockerfile index 46b420eeb9..4ccf83f64d 100755 --- a/dockerfile-dev-with-volumes/pod-encode/Dockerfile +++ b/dockerfile-dev-with-volumes/pod-encode/Dockerfile @@ -16,11 +16,14 @@ COPY ./pod/ . # TODO #FROM harbor.urba.univ-lille.fr/store/python:3.7-buster -RUN apt-get clean && apt-get update \ - && apt-get install -y netcat \ - ffmpeg \ - ffmpegthumbnailer \ - imagemagick +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + netcat \ + ffmpeg \ + ffmpegthumbnailer \ + imagemagick \ + && apt-get clean\ + && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app diff --git a/dockerfile-dev-with-volumes/pod-transcript/Dockerfile b/dockerfile-dev-with-volumes/pod-transcript/Dockerfile index 008f823cb0..a549ea5822 100755 --- a/dockerfile-dev-with-volumes/pod-transcript/Dockerfile +++ b/dockerfile-dev-with-volumes/pod-transcript/Dockerfile @@ -16,10 +16,13 @@ COPY ./pod/ . # TODO #FROM harbor.urba.univ-lille.fr/store/python:3.7-buster -RUN apt-get clean && apt-get update \ - && apt-get install -y netcat \ - sox \ - libsox-fmt-mp3 +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + netcat \ + sox \ + libsox-fmt-mp3 \ + && apt-get clean\ + && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app diff --git a/dockerfile-dev-with-volumes/pod-xapi/Dockerfile b/dockerfile-dev-with-volumes/pod-xapi/Dockerfile index ef1da5e0d0..15e8e3f451 100755 --- a/dockerfile-dev-with-volumes/pod-xapi/Dockerfile +++ b/dockerfile-dev-with-volumes/pod-xapi/Dockerfile @@ -16,7 +16,10 @@ COPY ./pod/ . # TODO #FROM harbor.urba.univ-lille.fr/store/python:3.7-buster -RUN apt-get clean && apt-get update && apt-get install -y netcat +RUN apt-get update \ + && apt-get install -y --no-install-recommends netcat\ + && apt-get clean\ + && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app diff --git a/dockerfile-dev-with-volumes/pod/Dockerfile b/dockerfile-dev-with-volumes/pod/Dockerfile index f459305a76..18197ab219 100755 --- a/dockerfile-dev-with-volumes/pod/Dockerfile +++ b/dockerfile-dev-with-volumes/pod/Dockerfile @@ -19,12 +19,15 @@ FROM $PYTHON_VERSION # TODO #FROM harbor.urba.univ-lille.fr/store/python:3.7-buster -RUN apt-get clean && apt-get update \ - && apt-get install -y netcat \ - ffmpeg \ - ffmpegthumbnailer \ - imagemagick \ - gettext +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + netcat \ + ffmpeg \ + ffmpegthumbnailer \ + imagemagick \ + gettext \ + && apt-get clean\ + && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app diff --git a/pod/authentication/admin.py b/pod/authentication/admin.py index e22f86eb6e..80967ad8c3 100644 --- a/pod/authentication/admin.py +++ b/pod/authentication/admin.py @@ -119,7 +119,7 @@ def get_readonly_fields(self, request, obj=None): self.readonly_fields += ("is_superuser",) return self.readonly_fields - def owner_hashkey(self, obj): + def owner_hashkey(self, obj) -> str: return "%s" % Owner.objects.get(user=obj).hashkey def formfield_for_manytomany(self, db_field, request, **kwargs): @@ -130,7 +130,7 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): kwargs["widget"] = widgets.FilteredSelectMultiple(db_field.verbose_name, False) return super().formfield_for_foreignkey(db_field, request, **kwargs) - def owner_establishment(self, obj): + def owner_establishment(self, obj) -> str: return "%s" % Owner.objects.get(user=obj).establishment owner_establishment.short_description = _("Establishment") @@ -146,7 +146,7 @@ def get_queryset(self, request): qs = qs.filter(owner__sites=get_current_site(request)) return qs - def save_model(self, request, obj, form, change): + def save_model(self, request, obj, form, change) -> None: super().save_model(request, obj, form, change) if not change: obj.owner.sites.add(get_current_site(request)) @@ -174,7 +174,7 @@ def get_queryset(self, request): qs = qs.filter(groupsite__sites=get_current_site(request)) return qs - def save_model(self, request, obj, form, change): + def save_model(self, request, obj, form, change) -> None: super().save_model(request, obj, form, change) if not change: obj.groupsite.sites.add(get_current_site(request)) diff --git a/pod/authentication/apps.py b/pod/authentication/apps.py index b753a5727a..ed200d7946 100644 --- a/pod/authentication/apps.py +++ b/pod/authentication/apps.py @@ -1,10 +1,11 @@ +"""Esup-Pod Authentication apps.""" from django.apps import AppConfig from django.db.models.signals import post_migrate from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import gettext_lazy as _ -def create_groupsite_if_not_exists(g): +def create_groupsite_if_not_exists(g) -> None: from pod.authentication.models import GroupSite try: @@ -13,7 +14,7 @@ def create_groupsite_if_not_exists(g): GroupSite.objects.create(group=g) -def set_default_site(sender, **kwargs): +def set_default_site(sender, **kwargs) -> None: from pod.authentication.models import Owner from django.contrib.sites.models import Site from django.contrib.auth.models import Group @@ -22,11 +23,11 @@ def set_default_site(sender, **kwargs): for g in Group.objects.all(): create_groupsite_if_not_exists(g) for gs in GroupSite.objects.all(): - if len(gs.sites.all()) == 0: + if gs.sites.count() == 0: gs.sites.add(Site.objects.get_current()) gs.save() for owner in Owner.objects.all(): - if len(owner.sites.all()) == 0: + if owner.sites.count() == 0: owner.sites.add(Site.objects.get_current()) owner.save() @@ -36,5 +37,5 @@ class AuthConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" verbose_name = _("Authentication") - def ready(self): + def ready(self) -> None: post_migrate.connect(set_default_site, sender=self) diff --git a/pod/authentication/backends.py b/pod/authentication/backends.py index 3385bb704d..816916fd04 100644 --- a/pod/authentication/backends.py +++ b/pod/authentication/backends.py @@ -15,7 +15,7 @@ CREATE_GROUP_FROM_AFFILIATION = getattr(settings, "CREATE_GROUP_FROM_AFFILIATION", False) -def is_staff_affiliation(affiliation): +def is_staff_affiliation(affiliation) -> bool: """Check if user affiliation correspond to AFFILIATION_STAFF.""" return affiliation in AFFILIATION_STAFF @@ -48,7 +48,7 @@ def authenticate(self, request, remote_user, shib_meta): return user if self.user_can_authenticate(user) else None @staticmethod - def update_owner_params(user, params): + def update_owner_params(user, params) -> None: """Update owner params from Shibboleth.""" user.owner.auth_type = "Shibboleth" if get_current_site(None) not in user.owner.sites.all(): diff --git a/pod/authentication/context_processors.py b/pod/authentication/context_processors.py index 927e6fab44..c4402a1d09 100644 --- a/pod/authentication/context_processors.py +++ b/pod/authentication/context_processors.py @@ -1,9 +1,10 @@ +"""Esup-Pod authentication context_processors.""" from django.conf import settings as django_settings SHIB_NAME = getattr(django_settings, "SHIB_NAME", "Identify Federation") -def context_authentication_settings(request): +def context_authentication_settings(request) -> dict: new_settings = {} new_settings["SHIB_NAME"] = SHIB_NAME return new_settings diff --git a/pod/authentication/forms.py b/pod/authentication/forms.py index e920bae91d..11159d5a5b 100644 --- a/pod/authentication/forms.py +++ b/pod/authentication/forms.py @@ -15,7 +15,7 @@ class OwnerAdminForm(forms.ModelForm): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super(OwnerAdminForm, self).__init__(*args, **kwargs) if __FILEPICKER__: self.fields["userpicture"].widget = CustomFileWidget(type="image") @@ -26,7 +26,7 @@ class Meta(object): class GroupSiteAdminForm(forms.ModelForm): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super(GroupSiteAdminForm, self).__init__(*args, **kwargs) class Meta(object): @@ -52,7 +52,7 @@ class Meta(object): class SetNotificationForm(forms.ModelForm): """Push notification preferences form.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super(SetNotificationForm, self).__init__(*args, **kwargs) class Meta(object): @@ -79,7 +79,7 @@ class Meta: fields = "__all__" exclude = [] - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: # Do the normal form initialisation. super(GroupAdminForm, self).__init__(*args, **kwargs) # If it is an existing group (saved objects have a pk). @@ -90,7 +90,7 @@ def __init__(self, *args, **kwargs): owner__sites=Site.objects.get_current() ) - def save_m2m(self): + def save_m2m(self) -> None: # Add the users to the Group. self.instance.user_set.set(self.cleaned_data["users"]) diff --git a/pod/authentication/models.py b/pod/authentication/models.py index 8db9f8be44..a09a6541ef 100644 --- a/pod/authentication/models.py +++ b/pod/authentication/models.py @@ -66,7 +66,7 @@ FILES_DIR = getattr(settings, "FILES_DIR", "files") -def get_name(self) -> str: +def get_name(self: User) -> str: """ Return the user's full name, including the username if not hidden. @@ -119,7 +119,7 @@ class Meta: verbose_name_plural = _("Owners") ordering = ["user"] - def __str__(self): + def __str__(self) -> str: if HIDE_USERNAME: return "%s %s" % (self.user.first_name, self.user.last_name) return "%s %s (%s)" % ( @@ -128,13 +128,13 @@ def __str__(self): self.user.username, ) - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: self.hashkey = hashlib.sha256( (SECRET_KEY + self.user.username).encode("utf-8") ).hexdigest() super(Owner, self).save(*args, **kwargs) - def is_manager(self): + def is_manager(self) -> bool: group_ids = ( self.user.groups.all() .filter(groupsite__sites=Site.objects.get_current()) @@ -146,18 +146,18 @@ def is_manager(self): ) @property - def email(self): + def email(self) -> str: return self.user.email @receiver(post_save, sender=Owner) -def default_site_owner(sender, instance, created, **kwargs): - if len(instance.sites.all()) == 0: +def default_site_owner(sender, instance, created: bool, **kwargs) -> None: + if instance.sites.count() == 0: instance.sites.add(Site.objects.get_current()) @receiver(post_save, sender=User) -def create_owner_profile(sender, instance, created, **kwargs): +def create_owner_profile(sender, instance, created: bool, **kwargs) -> None: if created: try: Owner.objects.create(user=instance) @@ -179,13 +179,13 @@ class Meta: @receiver(post_save, sender=GroupSite) -def default_site_groupsite(sender, instance, created, **kwargs): - if len(instance.sites.all()) == 0: +def default_site_groupsite(sender, instance, created: bool, **kwargs) -> None: + if instance.sites.count() == 0: instance.sites.add(Site.objects.get_current()) @receiver(post_save, sender=Group) -def create_groupsite_profile(sender, instance, created, **kwargs): +def create_groupsite_profile(sender, instance, created: bool, **kwargs) -> None: if created: try: GroupSite.objects.create(group=instance) @@ -211,7 +211,7 @@ class AccessGroup(models.Model): through="Owner_accessgroups", ) - def __str__(self): + def __str__(self) -> str: return "%s" % (self.display_name) class Meta: diff --git a/pod/authentication/populatedCASbackend.py b/pod/authentication/populatedCASbackend.py index 76cca6ddee..d3aa4aff92 100644 --- a/pod/authentication/populatedCASbackend.py +++ b/pod/authentication/populatedCASbackend.py @@ -73,7 +73,7 @@ __SUBTREE__ = "SUBTREE" -def populateUser(tree): +def populateUser(tree) -> None: """Populate user form CAS or LDAP attributes.""" username_element = tree.find( ".//{http://www.yale.edu/tp/cas}%s" % AUTH_CAS_USER_SEARCH @@ -103,13 +103,13 @@ def populateUser(tree): populate_user_from_entry(user, owner, entry) -def delete_synchronized_access_group(owner): +def delete_synchronized_access_group(owner) -> None: groups_to_sync = AccessGroup.objects.filter(auto_sync=True) for group_to_sync in groups_to_sync: owner.accessgroup_set.remove(group_to_sync) -def get_server(): +def get_server() -> Server: if isinstance(LDAP_SERVER["url"], str): server = Server( LDAP_SERVER["url"], @@ -167,7 +167,7 @@ def get_entry(conn, username, list_value): return None -def assign_accessgroups(groups_element, user): +def assign_accessgroups(groups_element, user) -> None: for group in groups_element: if group in GROUP_STAFF: user.is_staff = True @@ -189,7 +189,7 @@ def assign_accessgroups(groups_element, user): pass -def create_accessgroups(user, tree_or_entry, auth_type): +def create_accessgroups(user, tree_or_entry, auth_type) -> None: groups_element = [] if auth_type == "cas": tree_groups_element = tree_or_entry.findall( @@ -229,7 +229,7 @@ def get_entry_value(entry, attribute, default): return default -def populate_user_from_entry(user, owner, entry): +def populate_user_from_entry(user, owner, entry) -> None: """Populate user and owner objects from the LDAP entry.""" if DEBUG: print(entry) @@ -260,7 +260,7 @@ def populate_user_from_entry(user, owner, entry): owner.save() -def populate_user_from_tree(user, owner, tree): +def populate_user_from_tree(user, owner, tree) -> None: """Populate user from CAS attributes.""" if DEBUG: print_xml_tree(tree) @@ -312,13 +312,13 @@ def populate_user_from_tree(user, owner, tree): owner.save() -def print_xml_tree(tree): +def print_xml_tree(tree) -> None: """Print XML tree for debug purpose.""" import xml.etree.ElementTree as ET - import xml.dom.minidom + from defusedxml import minidom import os - xml_string = xml.dom.minidom.parseString(ET.tostring(tree)).toprettyxml() + xml_string = minidom.parseString(ET.tostring(tree)).toprettyxml() xml_string = os.linesep.join( [s for s in xml_string.splitlines() if s.strip()] ) # remove the weird newline issue diff --git a/pod/authentication/tests/test_populated.py b/pod/authentication/tests/test_populated.py index 8939026a60..6fc9606f3a 100644 --- a/pod/authentication/tests/test_populated.py +++ b/pod/authentication/tests/test_populated.py @@ -107,7 +107,7 @@ class PopulatedCASTestCase(TestCase): """ - def setUp(self): + def setUp(self) -> None: """Set up PopulatedCASTestCase create user Pod.""" User.objects.create(username="pod", password="pod1234pod") AccessGroup.objects.create(code_name="groupTest", display_name="Group de test") @@ -117,7 +117,7 @@ def setUp(self): print(" ---> SetUp of PopulatedCASTestCase: OK!") @override_settings(DEBUG=False) - def test_populate_user_from_tree(self): + def test_populate_user_from_tree(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -141,7 +141,7 @@ def test_populate_user_from_tree(self): ) @override_settings(DEBUG=False, CREATE_GROUP_FROM_AFFILIATION=True) - def test_populate_user_from_tree_affiliation(self): + def test_populate_user_from_tree_affiliation(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -162,7 +162,7 @@ def test_populate_user_from_tree_affiliation(self): CREATE_GROUP_FROM_AFFILIATION=True, CREATE_GROUP_FROM_GROUPS=True, ) - def test_populate_user_from_tree_affiliation_group(self): + def test_populate_user_from_tree_affiliation_group(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -191,7 +191,7 @@ def test_populate_user_from_tree_affiliation_group(self): CREATE_GROUP_FROM_GROUPS=True, USER_CAS_MAPPING_ATTRIBUTES=USER_CAS_MAPPING_ATTRIBUTES_TEST_NOGROUPS, ) - def test_populate_user_from_tree_affiliation_nogroup(self): + def test_populate_user_from_tree_affiliation_nogroup(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -220,7 +220,7 @@ def test_populate_user_from_tree_affiliation_nogroup(self): CREATE_GROUP_FROM_GROUPS=True, POPULATE_USER="CAS", ) - def test_populate_user_from_tree_unpopulate_group(self): + def test_populate_user_from_tree_unpopulate_group(self) -> None: user = User.objects.get(username="pod") user.owner.accessgroup_set.add(AccessGroup.objects.get(code_name="groupTest")) user.owner.accessgroup_set.add(AccessGroup.objects.get(code_name="groupTest2")) @@ -294,7 +294,7 @@ class PopulatedLDAPTestCase(TestCase): } entry = "" - def setUp(self): + def setUp(self) -> None: """Set up PopulatedLDAPTestCase create user Pod.""" User.objects.create(username="pod", password="pod1234pod") AccessGroup.objects.create(code_name="groupTest", display_name="Group de test") @@ -321,7 +321,7 @@ def setUp(self): print(" ---> SetUp of PopulatedLDAPTestCase: OK!") @override_settings(DEBUG=False) - def test_populate_user_from_entry(self): + def test_populate_user_from_entry(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -344,7 +344,7 @@ def test_populate_user_from_entry(self): ) @override_settings(DEBUG=False, CREATE_GROUP_FROM_AFFILIATION=True) - def test_populate_user_from_entry_affiliation(self): + def test_populate_user_from_entry_affiliation(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -364,7 +364,7 @@ def test_populate_user_from_entry_affiliation(self): CREATE_GROUP_FROM_AFFILIATION=True, CREATE_GROUP_FROM_GROUPS=True, ) - def test_populate_user_from_entry_affiliation_group(self): + def test_populate_user_from_entry_affiliation_group(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -393,7 +393,7 @@ def test_populate_user_from_entry_affiliation_group(self): CREATE_GROUP_FROM_GROUPS=True, POPULATE_USER="LDAP", ) - def test_populate_user_from_entry_unpopulate_group(self): + def test_populate_user_from_entry_unpopulate_group(self) -> None: user = User.objects.get(username="pod") user.owner.accessgroup_set.add(AccessGroup.objects.get(code_name="groupTest")) user.owner.accessgroup_set.add( @@ -432,7 +432,7 @@ def test_populate_user_from_entry_unpopulate_group(self): class PopulatedShibTestCase(TestCase): - def setUp(self): + def setUp(self) -> None: """Set up PopulatedShibTestCase create user Pod.""" self.hmap = {} for a in SHIBBOLETH_ATTRIBUTE_MAP: @@ -456,13 +456,13 @@ def _authenticate_shib_user(self, u): fake_shib_header[self.hmap["affiliation"]] = u["affiliations"].split(";")[0] fake_shib_header[self.hmap["affiliations"]] = u["affiliations"] - """Get valid shib_meta from simulated shibboleth header """ + # Get valid shib_meta from simulated shibboleth header. request = RequestFactory().get("/", REMOTE_USER=u["username"]) request.META.update(**fake_shib_header) shib_meta, error = shibmiddleware.ShibbMiddleware.parse_attributes(request) self.assertFalse(error, "Generating shibboleth attribute mapping contains errors") - """Check user authentication """ + # Check user authentication. user = ShibbBackend.authenticate( ShibbBackend(), request=request, @@ -474,7 +474,7 @@ def _authenticate_shib_user(self, u): return (user, shib_meta) @override_settings(DEBUG=False) - def test_make_profile(self): + def test_make_profile(self) -> None: """Test if user attributes are retrieved.""" user, shib_meta = self._authenticate_shib_user( { @@ -490,7 +490,7 @@ def test_make_profile(self): self.assertEqual(user.first_name, "John") self.assertEqual(user.last_name, "Do") - """Test if user can be staff if SHIBBOLETH_STAFF_ALLOWED_DOMAINS is None """ + # Test if user can be staff if SHIBBOLETH_STAFF_ALLOWED_DOMAINS is None. settings.SHIBBOLETH_STAFF_ALLOWED_DOMAINS = None reload(shibmiddleware) shibmiddleware.ShibbMiddleware.make_profile( @@ -524,7 +524,7 @@ def test_make_profile(self): ) self.assertTrue(user.is_staff) - """Test if same user with new unstaffable affiliation keep his staff status """ + # Test if same user with new unstaffable affiliation keep his staff status. for a in UNSTAFFABLE_AFFILIATIONS: self.assertFalse(a in AFFILIATION_STAFF) user, shib_meta = self._authenticate_shib_user( @@ -546,7 +546,7 @@ def test_make_profile(self): owner = Owner.objects.get(user__username="jdo@univ.fr") self.assertEqual(owner.affiliation, "member") - """Test if a new user with same unstaffable affiliations has no staff status""" + # Test if a new user with same unstaffable affiliations has no staff status. user, shib_meta = self._authenticate_shib_user( { "username": "ada@univ.fr", diff --git a/pod/authentication/tests/test_views.py b/pod/authentication/tests/test_views.py index 2efe8071d7..7ead289664 100644 --- a/pod/authentication/tests/test_views.py +++ b/pod/authentication/tests/test_views.py @@ -34,7 +34,7 @@ def test_authentication_login_gateway(self): of authenticationViewsTestCase: OK!" ) - def test_authentication_login(self): + def test_authentication_login(self) -> None: """Test authentication login page.""" self.client = Client() self.user = User.objects.get(username="pod") @@ -55,7 +55,7 @@ def test_authentication_login(self): of authenticationViewsTestCase: OK!" ) - def test_authentication_logout(self): + def test_authentication_logout(self) -> None: self.client = Client() # USE_CAS is valued to False response = self.client.get("/authentication_logout/") diff --git a/pod/bbb/bbb.py b/pod/bbb/bbb.py index f34283f5af..b218b2b81a 100644 --- a/pod/bbb/bbb.py +++ b/pod/bbb/bbb.py @@ -33,7 +33,7 @@ log = logging.getLogger(__name__) -def start_bbb_encode(id): +def start_bbb_encode(id) -> None: if CELERY_TO_ENCODE: task_start_bbb_encode.delay(id) else: @@ -43,7 +43,7 @@ def start_bbb_encode(id): t.start() -def bbb_encode_meeting(id): +def bbb_encode_meeting(id) -> None: msg = "" # Get the meeting diff --git a/pod/bbb/forms.py b/pod/bbb/forms.py index 69824b7b4e..0a727a7521 100644 --- a/pod/bbb/forms.py +++ b/pod/bbb/forms.py @@ -16,7 +16,7 @@ class MeetingForm(forms.ModelForm): - def __init__(self, request, *args, **kwargs): + def __init__(self, request, *args, **kwargs) -> None: super(MeetingForm, self).__init__(*args, **kwargs) # All fields are hidden. This form is like a Confirm prompt @@ -43,7 +43,7 @@ class Meta: @receiver(post_save, sender=BBB_Meeting) -def launch_encode(sender, instance, created, **kwargs): +def launch_encode(sender, instance, created, **kwargs) -> None: # Useful when an administrator send a re-encode task if hasattr(instance, "launch_encode") and instance.launch_encode is True: instance.launch_encode = False @@ -62,7 +62,7 @@ def launch_encode(sender, instance, created, **kwargs): class LivestreamForm(forms.ModelForm): - def __init__(self, request, *args, **kwargs): + def __init__(self, request, *args, **kwargs) -> None: super(LivestreamForm, self).__init__(*args, **kwargs) # All fields are hidden. This form is like a Confirm prompt diff --git a/pod/bbb/models.py b/pod/bbb/models.py index aafd4e5d2a..bbe60ffdfc 100644 --- a/pod/bbb/models.py +++ b/pod/bbb/models.py @@ -1,3 +1,4 @@ +"""Esup-Pod BBB models.""" import importlib from django.db import models @@ -85,13 +86,13 @@ class BBB_Meeting(models.Model): help_text=_("Last date where BBB session was in progress."), ) - def __unicode__(self): + def __unicode__(self) -> str: return "%s - %s" % (self.meeting_name, self.meeting_id) - def __str__(self): + def __str__(self) -> str: return "%s - %s" % (self.meeting_name, self.meeting_id) - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: super(BBB_Meeting, self).save(*args, **kwargs) class Meta: @@ -101,7 +102,7 @@ class Meta: @receiver(post_save, sender=BBB_Meeting) -def process_recording(sender, instance, created, **kwargs): +def process_recording(sender, instance, created, **kwargs) -> None: # Convert the BBB presentation only one time # Be careful: this is the condition to create a video of the # BigBlueButton presentation only one time. @@ -148,13 +149,13 @@ class Attendee(models.Model): help_text=_("User from the Pod database, if user found."), ) - def __unicode__(self): + def __unicode__(self) -> str: return "%s - %s" % (self.full_name, self.role) - def __str__(self): + def __str__(self) -> str: return "%s - %s" % (self.full_name, self.role) - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: super(Attendee, self).save(*args, **kwargs) class Meta: @@ -270,13 +271,13 @@ class Livestream(models.Model): help_text=_("Redis channel, useful for chat"), ) - def __unicode__(self): + def __unicode__(self) -> str: return "%s - %s" % (self.meeting, self.status) - def __str__(self): + def __str__(self) -> str: return "%s - %s" % (self.meeting, self.status) - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: super(Livestream, self).save(*args, **kwargs) class Meta: diff --git a/pod/chapter/static/js/chapters.js b/pod/chapter/static/js/chapters.js index 5cb8d76d88..f0dee55b35 100644 --- a/pod/chapter/static/js/chapters.js +++ b/pod/chapter/static/js/chapters.js @@ -12,10 +12,20 @@ global player */ +// Read-only globals defined in main.js +/* +global fadeIn +*/ + var id_form = "form_chapter"; + +/** + * Display the chapter form + * @param {*} data + */ function show_form(data) { let form_chapter = document.getElementById(id_form); - form_chapter.style.display = "none"; + form_chapter.classList.add("d-none"); form_chapter.innerHTML = data; form_chapter.querySelectorAll("script").forEach((item) => { // run script tags of filewidget.js and custom_filewidget.js @@ -66,19 +76,6 @@ function show_form(data) { } } -var showalert = function (message, alerttype) { - document.body.append( - '
create_archive_package
command "
- "without the --dry
option to delete them from %s."
+ "your ARCHIVE_ROOT folder. Run the create_archive_package
"
+ "command without the --dry
option to delete them from %s."
)
% __TITLE_SITE__
)
diff --git a/pod/video/models.py b/pod/video/models.py
index 24f6de1434..90d902005f 100644
--- a/pod/video/models.py
+++ b/pod/video/models.py
@@ -626,8 +626,8 @@ class Meta:
@receiver(post_save, sender=Type)
-def default_site_type(sender, instance, created, **kwargs) -> None:
- if len(instance.sites.all()) == 0:
+def default_site_type(sender, instance, created: bool, **kwargs) -> None:
+ if instance.sites.count() == 0:
instance.sites.add(Site.objects.get_current())
@@ -1421,8 +1421,8 @@ class Meta:
@receiver(post_save, sender=Video)
-def default_site(sender, instance, created, **kwargs) -> None:
- if len(instance.sites.all()) == 0:
+def default_site(sender, instance, created: bool, **kwargs) -> None:
+ if instance.sites.count() == 0:
instance.sites.add(Site.objects.get_current())
@@ -1443,7 +1443,7 @@ def video_files_removal(sender, instance, using, **kwargs) -> None:
encoding.delete()
-def remove_video_file(video) -> None:
+def remove_video_file(video: Video) -> None:
"""Remove video file linked to video."""
if video.overview:
image_overview = os.path.join(
diff --git a/pod/video/static/css/videojs-controlbar.css b/pod/video/static/css/videojs-controlbar.css
index d1f8304d6f..224fd41f02 100644
--- a/pod/video/static/css/videojs-controlbar.css
+++ b/pod/video/static/css/videojs-controlbar.css
@@ -1,5 +1,5 @@
/*
- * Video JS control-bar Custom CSS for EsupPod
+ * Video JS control-bar Custom CSS for Esup-Pod
*/
/* add Highlighting for active buttons */
diff --git a/pod/video/static/js/ajax-display-channels.js b/pod/video/static/js/ajax-display-channels.js
index bcd64713e7..88e635ce5b 100644
--- a/pod/video/static/js/ajax-display-channels.js
+++ b/pod/video/static/js/ajax-display-channels.js
@@ -30,7 +30,7 @@ function setAttributesWithTab(htmlElement, attributeCouples) {
*/
function getChannelsForSpecificChannelTabs(page, id = 0) {
let url = "";
- if (id == 0)
+ if (id === 0)
url = `${GET_CHANNELS_FOR_SPECIFIC_CHANNEL_TAB_REQUEST_URL}?page=${page}`;
else
url = `${GET_CHANNELS_FOR_SPECIFIC_CHANNEL_TAB_REQUEST_URL}?page=${page}&id=${id}`;
@@ -185,7 +185,7 @@ function convertToModalListElement(channel) {
const noWrapSpanElement = document.createElement("span");
noWrapSpanElement.classList.add("text-nowrap");
if (haveThemes) {
- spanElement = setChannelThemeButtonForModal(noWrapSpanElement, channel);
+ var spanElement = setChannelThemeButtonForModal(noWrapSpanElement, channel);
}
var videoString = ngettext(
diff --git a/pod/video/static/js/change_video_owner.js b/pod/video/static/js/change_video_owner.js
index 5628334e39..93be1582a3 100644
--- a/pod/video/static/js/change_video_owner.js
+++ b/pod/video/static/js/change_video_owner.js
@@ -112,7 +112,7 @@
e.stopPropagation();
div.remove();
});
- container.innerHTML = "";
+ container.textContent = "";
container.appendChild(div);
};
@@ -132,7 +132,7 @@
}
if (remove) loader.remove();
else if (!container.querySelector(".spinner-border") && !remove) {
- container.innerHTML = "";
+ container.textContent = "";
container.appendChild(loader);
}
};
@@ -188,7 +188,7 @@
return;
}
const curr_offset = Number.parseInt(
- getSearchParamFromURL(url, "offset", 0),
+ getSearchParamFromURL(url, "offset", 0), 10
);
const curr_page = curr_offset === 0 ? 1 : 1 + curr_offset / limit;
const total_page = Math.ceil(DATA.count / limit);
@@ -256,7 +256,7 @@
* @param {HTMLElement} suggestion suggestion container
*/
const clearSuggestions = (suggestion) => {
- suggestion.innerHTML = "";
+ suggestion.textContent = "";
};
/**
@@ -295,7 +295,7 @@
let once = false;
input.addEventListener("keyup", (e) => {
if (
- (/[a-z0-9\s]/.test(e.key.toLowerCase()) && e.key.length == 1) ||
+ (/[a-z0-9\s]/.test(e.key.toLowerCase()) && e.key.length === 1) ||
e.key.toLowerCase() == "backspace"
) {
const is_filter_input = input.isEqualNode(list_videos__search);
@@ -426,7 +426,7 @@
* @param {Array} videos list of videos
*/
const refreshVideos = (url, videos = DATA.results) => {
- videos_container.innerHTML = "";
+ videos_container.textContent = "";
videos.forEach((video) => {
const cls = choosed_videos.includes(video.id) ? "choosed" : "";
const card = document.createElement("DIV");
@@ -521,7 +521,7 @@
current_username_filter !== username &&
input.isEqualNode(old_owner_input)
) {
- videos_container.innerHTML = "";
+ videos_container.textContent = "";
reset(true);
}
};
@@ -665,9 +665,9 @@
}
selectAllVideos.checked = false;
DATA = { count: 0, next: null, previous: null, results: [] };
- videos_container.innerHTML = "";
+ videos_container.textContent = "";
list_videos__search.value = "";
- list_videos__search.nextElementSibling.innerHTML = "";
+ list_videos__search.nextElementSibling.textContent = "";
choosed_videos = [];
refreshPageInfos(null);
refreshPagination();
diff --git a/pod/video/static/js/comment-script.js b/pod/video/static/js/comment-script.js
index aa082cf7e5..6013a42587 100644
--- a/pod/video/static/js/comment-script.js
+++ b/pod/video/static/js/comment-script.js
@@ -1,5 +1,5 @@
/**
- * comment-scripts.js
+ * @file Esup-Pod comment scripts
* Handle comments under a video in Esup-Pod
*/
@@ -328,10 +328,10 @@ class Comment extends HTMLElement {
`;
let new_comment = add_comment.querySelector(".new_comment");
let comment_reply_btn = add_comment.querySelector(".send_reply");
- comment_reply_btn.addEventListener("click", (e) => {
+ comment_reply_btn.addEventListener("click", () => {
this.submit_comment_reply(new_comment, add_comment, comment_reply_btn);
});
- new_comment.addEventListener("keyup", function (e) {
+ new_comment.addEventListener("keyup", function () {
// Enable/Disable send button
if (
this.value.trim().length > 0 &&
@@ -525,7 +525,7 @@ function vote(comment_action_html, comment_id) {
return response.json();
} else {
let message = gettext("Bad response from the server.");
- if (response.status == 403) {
+ if (response.status === 403) {
message = gettext("Sorry, you’re not allowed to vote by now.");
}
throw new Error(message);
@@ -615,7 +615,7 @@ function save_comment(
return response.json();
} else {
let message = gettext("Bad response from the server.");
- if (response.status == 403) {
+ if (response.status === 403) {
message = gettext("Sorry, you can’t comment this video by now.");
}
throw new Error(message);
@@ -753,7 +753,7 @@ function delete_comment(comment) {
});
} else {
let message = gettext("Bad response from the server.");
- if (response.status == 403) {
+ if (response.status === 403) {
message = gettext("Sorry, you can’t delete this comment by now.");
}
showalert(message, "alert-danger");
@@ -811,7 +811,7 @@ function getElementPosition(element) {
let yPosition = 0;
// get body padding top if exists that will be
let bodyPaddingTop = Number.parseInt(
- getComputedStyle(document.body)["padding-top"] ?? 0,
+ getComputedStyle(document.body)["padding-top"] ?? 0, 10
);
while (element) {
@@ -905,7 +905,7 @@ function add_user_tag(comment_value, parent_comment) {
*/
function setBorderLeftColor(comment, parent_element) {
try {
- let index = Number.parseInt(parent_element.dataset.level) + 1;
+ let index = Number.parseInt(parent_element.dataset.level, 10) + 1;
if (index >= COLORS.length) {
comment.dataset.level = COLORS.length - 1;
comment.querySelector(".comment_content").style.borderLeft = `4px solid ${
@@ -1196,7 +1196,7 @@ function htmlContainsClass(html_el, classes) {
function get_node(el, class_name, not) {
class_name = class_name || "comment_container";
not = not || "";
- let selector = !!not ? `.${class_name}:not(.${not})` : `.${class_name}`;
+ let selector = not ? `.${class_name}:not(.${not})` : `.${class_name}`;
let foundedElement = el.querySelector(selector);
if (htmlContainsClass(el, class_name) && !htmlContainsClass(el, not)) {
return el;
diff --git a/pod/video/static/js/dashboard.js b/pod/video/static/js/dashboard.js
index b1030e0bbc..0e6d39315c 100644
--- a/pod/video/static/js/dashboard.js
+++ b/pod/video/static/js/dashboard.js
@@ -18,6 +18,8 @@
global urlUpdateVideos csrftoken formFieldsets displayMode
*/
+/* exported dashboardActionReset */
+
var bulkUpdateActionSelect = document.getElementById("bulkUpdateActionSelect");
var applyBulkUpdateBtn = document.getElementById("applyBulkUpdateBtn");
var resetDashboardElementsBtn = document.getElementById(
@@ -92,8 +94,6 @@ confirmModalBtn.addEventListener("click", (e) => {
/**
* Reset action and value of dashboard form elements when reset button is clicked.
- *
- * exported dashboardActionReset
**/
function dashboardActionReset() {
dashboardAction = "";
diff --git a/pod/video/static/js/filter_aside_video_list_refresh.js b/pod/video/static/js/filter_aside_video_list_refresh.js
index af18da914d..acb55bbefb 100644
--- a/pod/video/static/js/filter_aside_video_list_refresh.js
+++ b/pod/video/static/js/filter_aside_video_list_refresh.js
@@ -2,6 +2,19 @@
* @file Esup-Pod functions for videos list refresh and aside manage.
* @since 3.4.1
*/
+
+// Read-only globals defined in filter-aside-element-list-refresh.js
+/* global toggleSortDirection */
+
+// Read-only globals defined ever in playlist.html, dashboard.html or videos.html
+/* global urlVideos */
+
+// Read-only globals defined ever in infinite.js
+/* global InfiniteLoader */
+
+// Read-only globals defined in video_select.js
+/* global setSelectedVideos */
+
var infinite;
var checkedInputs = [];
@@ -9,10 +22,10 @@ let infiniteLoading = document.querySelector(".infinite-loading");
let ownerBox = document.getElementById("ownerbox");
let filterOwnerContainer = document.getElementById("collapseFilterOwner");
-onBeforePageLoad = function () {
+function onBeforePageLoad() {
infiniteLoading.style.display = "block";
-};
-onAfterPageLoad = function () {
+}
+function onAfterPageLoad() {
if (
urlVideos === "/video/dashboard/" &&
selectedVideos &&
@@ -43,7 +56,7 @@ onAfterPageLoad = function () {
footer.classList.remove("fixed-bottom");
}
});
-};
+}
/**
* Refresh Infinite Loader (Waypoint Infinite's)
@@ -74,7 +87,7 @@ function replaceCountVideos(newCount) {
newCount,
);
videoFoundStr = interpolate(videoFoundStr, { count: newCount }, true);
- document.getElementById("video_count").innerHTML = videoFoundStr;
+ document.getElementById("video_count").textContent = videoFoundStr;
}
/**
@@ -82,7 +95,7 @@ function replaceCountVideos(newCount) {
*/
function refreshVideosSearch() {
// Erase videos list and show loader
- document.getElementById("videos_list").innerHTML = "";
+ document.getElementById("videos_list").textContent = "";
showLoader(videosListLoader, true);
let url = getUrlForRefresh();
// Async GET request wth parameters by fetch method
@@ -120,8 +133,8 @@ function refreshVideosSearch() {
setSelectedVideos();
}
})
- .catch((error) => {
- document.getElementById("videos_list").innerHTML = gettext(
+ .catch(() => {
+ document.getElementById("videos_list").textContent = gettext(
"An Error occurred while processing.",
);
})
@@ -197,11 +210,11 @@ function setListenerChangeInputs(el) {
* Add event listener to search user input to create checkboxes
*/
if (ownerBox) {
- ownerBox.addEventListener("input", (e) => {
+ ownerBox.addEventListener("input", () => {
if (ownerBox.value && ownerBox.value.length > 2) {
var searchTerm = ownerBox.value;
getSearchListUsers(searchTerm).then((users) => {
- filterOwnerContainer.innerHTML = "";
+ filterOwnerContainer.textContent = "";
users.forEach((user) => {
filterOwnerContainer.appendChild(createUserCheckBox(user));
setListenerChangeInputs(
@@ -210,7 +223,7 @@ if (ownerBox) {
});
});
} else {
- filterOwnerContainer.innerHTML = "";
+ filterOwnerContainer.textContent = "";
}
});
}
@@ -257,7 +270,7 @@ document.getElementById("resetFilters").addEventListener("click", function () {
c_p.classList.remove("active");
});
if (filterOwnerContainer && ownerBox) {
- filterOwnerContainer.innerHTML = "";
+ filterOwnerContainer.textContent = "";
ownerBox.value = "";
}
window.history.pushState("", "", window.location.pathname);
diff --git a/pod/video/static/js/regroup_videos_by_theme.js b/pod/video/static/js/regroup_videos_by_theme.js
index 16314da73d..8fee5ef7a1 100644
--- a/pod/video/static/js/regroup_videos_by_theme.js
+++ b/pod/video/static/js/regroup_videos_by_theme.js
@@ -1,6 +1,10 @@
/**
* @file Esup-Pod functions for regroup_videos_by_theme.
*/
+
+/* Read-only globals defined in channel.html */
+/* global has_more_themes */
+
import { Helper } from "/static/js/utils.js";
function run(has_more_themes, Helper) {
@@ -124,20 +128,17 @@ function run(has_more_themes, Helper) {