From 4c95110d66b56e55457048edae59f57ccdc8e20f Mon Sep 17 00:00:00 2001 From: Olivier Bado-Faustin Date: Mon, 6 May 2024 11:46:48 +0200 Subject: [PATCH] [DONE] Create archive packages (#1107) * First attempt to create archive packages * flake8 * Improve unarchive_video command * Markdown format complicance: * Add .markdownlint.json for markdownlint * Ignore MD033 (No inline HTML) * Set MD "line_length" to 90 chars * Correct "createconfiguration" command to apply markdown rules * Apply markdown rules to README, SECURITY, AUTHORS, CONTRIBUTING & CODE_OF_CONDUCT * * Add "Whisper" in config.json * Remove duplicated attribute in forms.py * Code Formatting * * Add "dry mode" for check_obsolete_videos command * Update create_archive command to add some video completions in json * Comment unused functions in chapter.js * Code formatting * Create_archive_package command now export additional documents and video tracks + replace deprecated function `utcfromtimestamp(start)` by `fromtimestamp(start, tz=timezone.utc)` + A11y : add titles on Public/private document icons + QoC (add some missing return types and pydocs) * Ask GG to ignore PWD in comptetion test_views * flake 8 compliance + add "#nosec" on test pwd to ignore it. * add missing param in create_archive_package + fix the completion views test * Add # nosem comments to tell semgrep to ignore some password validation checks in tests. * Disable commonjs & node in eslintrc to be able to add `/* exported my_global_var */` comment before some global vars. * Replace `from xml.dom import minidom` by `from defusedxml import minidom` to avoid security issue * Replace `from xml.dom import minidom` by `from defusedxml import minidom` to avoid security issue + code formatting * * Add sizeof_fmt() in main utils * print total duration and total weight * send mail to managers after create_archive_package * Replace xml.dom by defusedxml to enforce security * Export also AdvancedNotes and video comments * flake8 * Replace NODE 19 by 20 * Add i18n strings + revert defusedxml by xml.dom on test_utils, as there is no "Document()" in defusedxml * Revert defusedxml to dom.minidom in type_studio as 'defusedxml.minidom' has no attribute 'Text' * Add main utils tests & test_get_dublin_core + improve security of minidom in type_studio + escape xml entities in get_dublin_core() * Apply all PR comments * Flake8 compliance * correct test_generate_qrcode as generated qr code is not always the same. * Try to use coverallsapp/github-action * Include .mo compiled lang files + remove coveralls test --- .coveragerc | 1 + .eslintrc.js | 4 +- .gitattributes | 3 +- .github/pull_request_template.md | 2 +- .github/workflows/pod_dev.yml | 6 +- .markdownlint.json | 7 + AUTHORS.md | 24 +- CODE_OF_CONDUCT.md | 96 +-- CONTRIBUTING.md | 157 +++-- README.md | 51 +- SECURITY.md | 4 +- dockerfile-dev-with-volumes/README.adoc | 2 +- docs/index.md | 5 +- pod/authentication/tests/test_utils.py | 4 +- pod/bbb/management/commands/bbb.py | 2 +- pod/chapter/admin.py | 3 + pod/chapter/models.py | 44 +- pod/chapter/static/js/chapters.js | 22 +- pod/chapter/static/js/videojs-chapters.js | 2 +- pod/chapter/tests/test_views.py | 17 +- pod/chapter/utils.py | 4 +- pod/chapter/views.py | 1 + pod/completion/admin.py | 11 +- pod/completion/models.py | 141 ++--- .../templates/document/list_document.html | 10 +- .../templates/video_caption_maker.html | 12 +- .../templates/video_completion.html | 2 +- pod/completion/tests/test_models.py | 8 +- pod/completion/tests/test_views.py | 153 ++--- pod/completion/views.py | 4 +- pod/dressing/tests/test_utils.py | 6 +- pod/enrichment/models.py | 66 +- pod/import_video/views.py | 6 +- .../static/js/broadcaster_from_building.js | 8 +- pod/locale/fr/LC_MESSAGES/django.mo | Bin 201297 -> 202751 bytes pod/locale/fr/LC_MESSAGES/django.po | 243 +++++--- pod/locale/fr/LC_MESSAGES/djangojs.mo | Bin 20963 -> 20854 bytes pod/locale/fr/LC_MESSAGES/djangojs.po | 20 +- pod/locale/nl/LC_MESSAGES/django.po | 166 ++++-- pod/locale/nl/LC_MESSAGES/djangojs.po | 81 ++- pod/main/apps.py | 8 +- pod/main/configuration.json | 564 ++++++++++-------- .../commands/createconfiguration.py | 80 ++- pod/main/models.py | 32 +- pod/main/tests/test_utils.py | 34 ++ pod/main/tests/test_views.py | 67 ++- pod/main/utils.py | 32 +- pod/meeting/models.py | 14 +- .../templates/meeting/add_or_edit.html | 2 +- .../templates/meeting/meeting_card.html | 6 +- pod/meeting/webinar_utils.py | 4 +- pod/playlist/tests/test_forms.py | 22 +- pod/podfile/static/podfile/js/filewidget.js | 227 +++---- pod/recorder/admin.py | 5 +- pod/recorder/models.py | 41 +- pod/recorder/plugins/type_audiovideocast.py | 10 +- pod/recorder/plugins/type_studio.py | 29 +- pod/recorder/tests/test_plugins.py | 2 +- pod/recorder/tests/test_utils.py | 27 +- pod/recorder/tests/test_views.py | 2 +- pod/recorder/utils.py | 2 +- pod/recorder/views.py | 2 +- pod/video/forms.py | 1 - .../commands/check_obsolete_videos.py | 98 +-- .../management/commands/clean_video_files.py | 10 +- .../commands/create_archive_package.py | 311 ++++++++++ pod/video/management/commands/encode.py | 8 +- .../commands/migrate_bbb_recordings.py | 2 +- pod/video/management/commands/recorder.py | 2 +- .../management/commands/unarchive_video.py | 79 ++- pod/video/models.py | 138 ++--- pod/video/rest_views.py | 11 +- pod/video/static/js/dashboard.js | 4 +- pod/video/static/js/video_select.js | 3 +- .../templates/videos/dashboard_modal.html | 2 +- pod/video/templates/videos/dublincore.html | 12 +- pod/video/templates/videos/video-iframe.html | 2 +- pod/video/tests/test_feeds.py | 2 +- pod/video/tests/test_models.py | 164 +++-- pod/video/utils.py | 10 + pod/video/views.py | 4 +- .../tests/test_utils.py | 2 +- pod/video_search/utils.py | 6 +- requirements.txt | 1 + setup.cfg | 7 + 85 files changed, 2200 insertions(+), 1289 deletions(-) create mode 100644 .markdownlint.json create mode 100644 pod/main/tests/test_utils.py create mode 100644 pod/video/management/commands/create_archive_package.py diff --git a/.coveragerc b/.coveragerc index ce808f4bfb..672d52d2bc 100755 --- a/.coveragerc +++ b/.coveragerc @@ -9,6 +9,7 @@ source = pod/ omit = pod/*settings*.py pod/*apps.py pod/wsgi.py + pod/db_migrations/* */tests/* */commands/* */migrations/* diff --git a/.eslintrc.js b/.eslintrc.js index 5409490e58..cb0dc50957 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,9 +1,9 @@ module.exports = { "env": { "browser": true, - "commonjs": true, + "commonjs": false, "es2021": true, - "node": true + "node": false }, "extends": "eslint:recommended", "parserOptions": { diff --git a/.gitattributes b/.gitattributes index a01fe71d39..e56871b2a3 100755 --- a/.gitattributes +++ b/.gitattributes @@ -12,4 +12,5 @@ # Désigne tous les fichiers qui sont vraiment binaires et ne doivent pas être modifiés. *.png binary -*.jpg binary \ No newline at end of file +*.jpg binary +*.webp binary diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1b86a134d3..eeaf24f26e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ -Before sending your pull request, make sure the following are done: +# Before sending your pull request, make sure the following are done * [ ] You have read our [contribution guidelines](https://github.com/EsupPortail/Esup-Pod/blob/master/CONTRIBUTING.md). * [ ] Your PR targets the `develop` branch. diff --git a/.github/workflows/pod_dev.yml b/.github/workflows/pod_dev.yml index e15429666a..ef0c0fa157 100644 --- a/.github/workflows/pod_dev.yml +++ b/.github/workflows/pod_dev.yml @@ -16,10 +16,10 @@ env: PYTHON_VERSION: '3.9' DJANGO_SUPERUSER_USERNAME: "admin" DJANGO_SUPERUSER_PASSWORD: "passwd" - DJANGO_SUPERUSER_EMAIL: "noreplay@uni.fr" + DJANGO_SUPERUSER_EMAIL: "noreply@uni.fr" ELASTICSEARCH_TAG: "elasticsearch:7.17.18" - NODE_VERSION: "19" - NODE_TAG: "node:19" + NODE_VERSION: "20" + NODE_TAG: "node:20" PYTHON_TAG: "python:3.9-bullseye" REDIS_TAG: "redis:alpine3.16" DOCKER_ENV: "full-test" diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000000..db2351e0fb --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,7 @@ +{ + "default": true, + "MD033": false, + "MD013": { + "line_length": 90 + } +} diff --git a/AUTHORS.md b/AUTHORS.md index 085248eab4..367c5d8a42 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,14 +1,22 @@ +Esup-Pod Authors +================ + Maintainer ---------- + [Esup Portail](https://www.esup-portail.org/) Original Authors ---------------- + * Nicolas Can, University of Lille, France ([@ptitloup](https://github.com/ptitloup)) Contributors for the V3 ---------------------------- -A list of much-appreciated contributors who have submitted patches and reported bugs for the V3: + +A list of much-appreciated contributors +who have submitted patches and reported bugs for the V3: + * Olivier Bado-Faustin, University Cote d'Azur (design and template) * Nicolas Lahoche, University of Lille (design and template) with all the PRI Team * Nathaniel Burlot, University of Lille (member of PRI team for Logo and color of V3) @@ -19,12 +27,15 @@ A list of much-appreciated contributors who have submitted patches and reported Partnership ---------------------------- + * Elygames * OrionStudio Previous Author/Contributors ---------------------------- + A list of much-appreciated contributors who have submitted patches and reported bugs: + * Joël Obled, Esup-Portail Consortium, France ([@DrClockwork](https://github.com/DrClockwork)) * Charlotte Benard (Logo and color of V2) * Frederic Sene, INSA Rennes @@ -35,6 +46,11 @@ A list of much-appreciated contributors who have submitted patches and reported Pictures credits ---------------------------- -* default.svg: adapted from Play button Icon by [Freepik](https://www.freepik.com/free-vector) - Freepik License -* cookie.svg: [Broken oatmeal cookie created by pch.vector](https://www.freepik.com/vectors/logo) - Freepik License -* default-playlist.svg: Music, Note, Musical Note by [krzysztof-m](https://pixabay.com/fr/users/1363864/) - [Pixabay free for use & download licence](https://pixabay.com/fr/service/terms/) + +* default.svg: adapted from Play button Icon + by [Freepik](https://www.freepik.com/free-vector) - Freepik License +* cookie.svg: + [oatmeal cookie created by pch.vector](https://www.freepik.com/vectors/logo) - Freepik License +* default-playlist.svg: Music, Note, Musical Note + by [krzysztof-m](https://pixabay.com/fr/users/1363864/) - + [Pixabay free for use & download licence](https://pixabay.com/fr/service/terms/) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 1a6c4d0de6..085908eaba 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,5 +1,4 @@ - -# Code de conduite _Esup-Pod_ +# Code de conduite *Esup-Pod* ## Notre engagement @@ -12,7 +11,8 @@ le niveau d'expérience, l'éducation, le statut socio-économique, la nationalité, l'apparence personnelle, la race, la religion, ou l'identité et l'orientation sexuelle. -Nous nous engageons à agir et interagir de manière à contribuer à une communauté ouverte, accueillante, diversifiée, inclusive et saine. +Nous nous engageons à agir et interagir de manière à contribuer à une communauté ouverte, +accueillante, diversifiée, inclusive et saine. ## Nos critères @@ -21,55 +21,73 @@ Exemples de comportements qui contribuent à créer un environnement positif : * Faire preuve d'empathie et de bienveillance envers les autres * Être respectueux des opinions, points de vue et expériences divergents * Donner et recevoir avec grâce les critiques constructives -* Assumer ses responsabilités et s'excuser auprès des personnes affectées par nos erreurs et apprendre de ces expériences -* Se concentrer sur ce qui est le meilleur non pas uniquement pour nous en tant qu'individu, mais aussi pour l'ensemble de la communauté +* Assumer ses responsabilités et s'excuser auprès des personnes + affectées par nos erreurs et apprendre de ces expériences +* Se concentrer sur ce qui est le meilleur non pas uniquement pour nous + en tant qu'individu, mais aussi pour l'ensemble de la communauté Exemples de comportements inacceptables : -* L'utilisation de langage ou d'images sexualisés et d'attentions ou d'avances sexuelles de toute nature -* Le _trolling_, les commentaires insultants ou désobligeants et les attaques +* L'utilisation de langage ou d'images sexualisés et d'attentions + ou d'avances sexuelles de toute nature +* Le *trolling*, les commentaires insultants ou désobligeants et les attaques personnelles ou d'ordre politique * Le harcèlement en public ou en privé * La publication d'informations privées d'autrui, telle qu'une adresse postale ou une adresse électronique, sans leur autorisation explicite -* Toute autre conduite qui pourrait raisonnablement être considérée comme inappropriée - dans un cadre professionnel +* Toute autre conduite qui pourrait raisonnablement + être considérée comme inappropriée dans un cadre professionnel ## Responsabilités d'application -Les dirigeant·e·s de la communauté sont chargé·e·s de clarifier et de faire respecter nos normes de -comportements acceptables et prendront des mesures correctives appropriées et équitables en -réponse à tout comportement qu'ils ou elles jugent inapproprié, menaçant, offensant ou nuisible. +Les dirigeant·e·s de la communauté sont chargé·e·s de clarifier +et de faire respecter nos normes de comportements acceptables +et prendront des mesures correctives appropriées et équitables en +réponse à tout comportement qu'ils ou elles jugent +inapproprié, menaçant, offensant ou nuisible. -Les dirigeant·e·s de la communauté ont le droit et la responsabilité de supprimer, modifier ou rejeter -les commentaires, les contributions, le code, les modifications de wikis, les rapports d'incidents ou de bogues et autres contributions qui -ne sont pas alignés sur ce code de conduite, et communiqueront les raisons des décisions de modération -le cas échéant. +Les dirigeant·e·s de la communauté ont le droit et la responsabilité de supprimer, +modifier ou rejeter les commentaires, +les contributions, le code, les modifications de wikis, +les rapports d'incidents ou de bogues et autres contributions qui +ne sont pas alignés sur ce code de conduite, +et communiqueront les raisons des décisions de modération le cas échéant. ## Portée d'application -Ce code de conduite s'applique à la fois au sein des espaces du projet ainsi que dans les espaces publics lorsqu'un individu représente officiellement le projet ou sa communauté. +Ce code de conduite s'applique à la fois au sein des espaces du projet +ainsi que dans les espaces publics lorsqu'un individu +représente officiellement le projet ou sa communauté. Font parties des exemples de représentation d'un projet ou d'une -communauté l'utilisation d'une adresse électronique officielle, la publication sur les réseaux sociaux à l'aide d'un compte officiel ou le fait d'agir en tant que représentant·e désigné·e lors d'un événement en ligne ou hors-ligne. +communauté l'utilisation d'une adresse électronique officielle, +la publication sur les réseaux sociaux à l'aide d'un compte officiel +ou le fait d'agir en tant que représentant·e désigné·e +lors d'un événement en ligne ou hors-ligne. ## Application Les cas de comportements abusifs, harcelants ou tout autre comportement -inacceptables peuvent être signalés aux dirigeant·e·s de la communauté responsables de l'application du code de conduite à +inacceptables peuvent être signalés aux dirigeant·e·s de la communauté +responsables de l'application du code de conduite à [Esup-Pod](https://github.com/EsupPortail/Esup-Pod). Toutes les plaintes seront examinées et feront l'objet d'une enquête rapide et équitable. -Tou·te·s les dirigeant·e·s de la communauté sont tenu·e·s de respecter la vie privée et la sécurité des personnes ayant signalé un incident. +Tou·te·s les dirigeant·e·s de la communauté sont tenu·e·s de +respecter la vie privée et la sécurité des personnes ayant signalé un incident. ## Directives d'application -Les dirigeant·e·s de communauté suivront ces directives d'application sur l'impact communautaire afin de déterminer les conséquences de toute action qu'ils jugent contraire au présent code de conduite : +Les dirigeant·e·s de communauté suivront ces directives d'application +sur l'impact communautaire afin de déterminer les conséquences de toute action +qu'ils jugent contraire au présent code de conduite : ### 1. Correction -**Impact communautaire** : utilisation d'un langage inapproprié ou tout autre comportement jugé non professionnel ou indésirable dans la communauté. +**Impact communautaire** : utilisation d'un langage inapproprié ou +tout autre comportement jugé non professionnel ou indésirable dans la communauté. -**Conséquence** : un avertissement écrit et privé de la part des dirigeant·e·s de la communauté, clarifiant la nature du non-respect et expliquant pourquoi +**Conséquence** : un avertissement écrit et privé de la part des +dirigeant·e·s de la communauté, clarifiant la nature du non-respect et expliquant pourquoi le comportement était inapproprié. Des excuses publiques peuvent être demandées. ### 2. Avertissement @@ -77,36 +95,44 @@ le comportement était inapproprié. Des excuses publiques peuvent être demand **Impact communautaire** : un non-respect par un seul incident ou une série d'actions. **Conséquence** : un avertissement avec des conséquences dû à la poursuite du comportement. -Aucune interaction avec les personnes concernées, y compris l'interaction non sollicitée avec celles et ceux qui sont chargé·e·s de l'application de ce code de conduite, pendant une période déterminée. -Cela comprend le fait d'éviter les interactions dans les espaces communautaires ainsi que sur les canaux externes comme les médias sociaux. +Aucune interaction avec les personnes concernées, +y compris l'interaction non sollicitée avec celles et ceux qui sont +chargé·e·s de l'application de ce code de conduite, pendant une période déterminée. +Cela comprend le fait d'éviter les interactions dans les espaces communautaires +ainsi que sur les canaux externes comme les médias sociaux. Le non-respect de ces conditions peut entraîner un bannissement temporaire ou permanent. ### 3. Bannissement temporaire -**Impact communautaire** : un non-respect grave des normes communautaires, notamment un comportement inapproprié soutenu. +**Impact communautaire** : un non-respect grave des normes communautaires, +notamment un comportement inapproprié soutenu. -**Conséquence** : un bannissement temporaire de toutes formes d'interactions ou de communications avec la communauté pendant une période déterminée. -Aucune interaction publique ou privée avec les personnes concernées, y compris les interactions non sollicitées avec celles et ceux qui appliquent ce code de conduite, n'est autorisée pendant cette période. +**Conséquence** : un bannissement temporaire de toutes formes d'interactions +ou de communications avec la communauté pendant une période déterminée. +Aucune interaction publique ou privée avec les personnes concernées, +y compris les interactions non sollicitées avec celles et ceux qui appliquent +ce code de conduite, n'est autorisée pendant cette période. Le non-respect de ces conditions peut entraîner un bannissement permanent. ### 4. Bannissement permanent -**Impact communautaire** : démontrer un schéma récurrent de non-respect des normes de la communauté y compris un comportement inapproprié soutenu, le harcèlement d'un individu ainsi que l'agression ou le dénigrement de catégories d'individus. +**Impact communautaire** : démontrer un schéma récurrent de non-respect +des normes de la communauté y compris un comportement inapproprié soutenu, +le harcèlement d'un individu ainsi que l'agression ou le dénigrement de catégories d'individus. -**Conséquence** : un bannissement permanent de toutes formes d'interactions publiques au sein de la communauté. +**Conséquence** : un bannissement permanent +de toutes formes d'interactions publiques au sein de la communauté. ## Attributions Ce code de conduite est adapté du -[Contributor Covenant](https://www.contributor-covenant.org), version 2.0, -disponible à -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. +[Contributor Covenant][homepage], [version 2.0][v2.0]. Les Directives d'application ont été inspirées par le [Code of conduct enforcement ladder][Mozilla CoC] de Mozilla. -Pour obtenir des réponses aux questions courantes sur ce code de conduite, consultez la FAQ à [https://www.contributor-covenant.org/faq][FAQ]. -Les traductions sont disponibles sur [https://www.contributor-covenant.org/translations][translations]. +Pour obtenir des réponses aux questions courantes sur ce code de conduite, consultez la [FAQ][FAQ]. +Des [traductions][translations] sont disponibles. [homepage]: https://www.contributor-covenant.org [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27a821ec73..29891ce196 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,10 +2,12 @@ :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: -The following is a set of guidelines for contributing to Pod, which is hosted in the [Esup Organization](https://github.com/EsupPortail) on GitHub. -These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. +The following is a set of guidelines for contributing to Pod, which is hosted +in the [Esup Organization](https://github.com/EsupPortail) on GitHub. +These are mostly guidelines, not rules. +Use your best judgment, and feel free to propose changes to this document in a pull request. -#### Table Of Contents +## Table of contents * [Code of Conduct](#code-of-conduct) @@ -21,94 +23,125 @@ These are mostly guidelines, not rules. Use your best judgment, and feel free to * [JavaScript Styleguide](#javascript-styleguide) * [Python Styleguide](#python-styleguide) - ## Code of Conduct This project and everyone participating in it is governed by the [Pod Code of Conduct](CODE_OF_CONDUCT.md). -By participating, you are expected to uphold this code. Please report unacceptable behavior to us. - -## I don't want to read this whole thing I just have a question!!! - -If chat is more your speed, you can join the Pod team on Rocket chat: https://rocket.esup-portail.org/channel/esup_-_pod +By participating, you are expected to uphold this code. +Please report unacceptable behavior to us. +## I don't want to read this whole thing I just have a question +If chat is more your speed, you can [join the Pod team on Rocket chat](https://rocket.esup-portail.org/channel/esup_-_pod). ## How Can I Contribute? ### Reporting Bugs This section guides you through submitting a bug report. -Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, +Following these guidelines helps maintainers and the +community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). -> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. - +> **Note:** If you find a **Closed** issue that seems like it is the same thing +that you're experiencing, open a new issue and include a link +to the original issue in the body of your new one. #### How Do I Submit A (Good) Bug Report? Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). -Create an issue and explain the problem and include additional details to help maintainers reproduce the problem: +Create an issue and explain the problem and include additional details +to help maintainers reproduce the problem: * **Use a clear and descriptive title** for the issue to identify the problem. * **Describe the exact steps which reproduce the problem** in as many details as possible. -* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. -* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. +* **Provide specific examples to demonstrate the steps**. Include links to files +or GitHub projects, or copy/pasteable snippets, which you use in those examples. +* **Describe the behavior you observed after following the steps** and point out +what exactly is the problem with that behavior. * **Explain which behavior you expected to see instead and why.** -* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. -* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below. -* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. +* **Include screenshots and animated GIFs** which show you following the described steps +and clearly demonstrate the problem. +You can use [this tool](https://www.cockos.com/licecap/) +to record GIFs on macOS and Windows, +and [this tool](https://github.com/colinkeenan/silentcast) +or [this tool](https://github.com/GNOME/byzanz) on Linux. +* **If the problem wasn't triggered by a specific action**, describe what you were doing +before the problem happened and share more information using the guidelines below. +* **Can you reliably reproduce the issue?** If not, provide details about +how often the problem happens and under which conditions it normally happens. Include details about your configuration and environment: * **Which version of Pod are you using?** * **What's the name and version of the browser you're using**? - ### Suggesting Enhancements -This section guides you through submitting an enhancement suggestion for Pod, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:. - +This section guides you through submitting an enhancement suggestion for Pod, +including completely new features and minor improvements to existing functionality. +Following these guidelines helps maintainers and the community understand +your suggestion :pencil: and find related suggestions :mag_right:. #### How Do I Submit A (Good) Enhancement Suggestion? -Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue and provide the following information: +Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). +Create an issue and provide the following information: * **Use a clear and descriptive title** for the issue to identify the suggestion. -* **Provide a step-by-step description of the suggested enhancement** in as many details as possible. -* **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). -* **Describe the current behavior** and **explain which behavior you expected to see instead** and why. -* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. +* **Provide a step-by-step description of the suggested enhancement** as many detailed as possible. +* **Provide specific examples to demonstrate the steps**. +Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). +* **Describe the current behavior** + and **explain which behavior you expected to see instead** and why. +* **Include screenshots and animated GIFs** which help you demonstrate the steps +or point out the part which the suggestion is related to. +You can use [this tool](https://www.cockos.com/licecap/) +to record GIFs on macOS and Windows, +and [this tool](https://github.com/colinkeenan/silentcast) +or [this tool](https://github.com/GNOME/byzanz) on Linux. * **Specify which version of Pod you're using.** * **Specify the name and version of the browser you're using.** - ### Pull Requests The process described here has several goals: -- Maintain quality -- Fix problems that are important to users -- Engage the community in working toward the best possible Pod -- Enable a sustainable system for maintainers to review contributions +* Maintain quality +* Fix problems that are important to users +* Engage the community in working toward the best possible Pod +* Enable a sustainable system for maintainers to review contributions Please follow these steps to have your contribution considered by the maintainers: 0. Follow the [styleguides](#styleguides) below. 1. Make sure that your pull request targets the `develop` branch. -2. Prefix the title of your pull request with one of the following : - * `[WIP]` if your pull request is still a work in progress. - * `[DONE]` if you are done with your patch. -3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing
What if the status checks are failing?If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.
- -While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted. +2. Prefix the title of your pull request with one of the following: + * `[WIP]` if your pull request is still a work in progress. + * `[DONE]` if you are done with your patch. +3. After you submit your pull request, verify that +all [status checks](https://help.github.com/articles/about-status-checks/) are passing + +
+What if the status checks are failing? +If a status check is failing, +and you believe that the failure is unrelated to your change, +please leave a comment on the pull request explaining +why you believe the failure is unrelated. +A maintainer will re-run the status check for you. +If we conclude that the failure was a false positive, +then we will open an issue to track that problem with our status check suite.
+ +While the prerequisites above must be satisfied prior to having your pull request reviewed, +the reviewer(s) may ask you to complete additional design work, tests, +or other changes before your pull request can be ultimately accepted. ## Styleguides ### Git config -Warning about the configuration of line ending: https://docs.github.com/fr/get-started/getting-started-with-git/configuring-git-to-handle-line-endings +Warning about the configuration of line ending: [configuring-git-to-handle-line-endings](https://docs.github.com/fr/get-started/getting-started-with-git/configuring-git-to-handle-line-endings) We add a .gitattributes file at the root of repository ### Git Commit Messages @@ -119,28 +152,34 @@ We add a .gitattributes file at the root of repository * Reference issues and pull requests liberally after the first line * When only changing documentation, include `[ci skip]` in the commit title * Consider starting the commit message with an applicable emoji: - * :art: `:art:` when improving the format/structure of the code - * :racehorse: `:racehorse:` when improving performance - * :non-potable_water: `:non-potable_water:` when plugging memory leaks - * :memo: `:memo:` when writing docs - * :bug: `:bug:` when fixing a bug - * :fire: `:fire:` when removing code or files - * :green_heart: `:green_heart:` when fixing the CI build - * :white_check_mark: `:white_check_mark:` when adding tests - * :lock: `:lock:` when dealing with security - * :arrow_up: `:arrow_up:` when upgrading dependencies - * :arrow_down: `:arrow_down:` when downgrading dependencies - * :shirt: `:shirt:` when removing linter warnings - + * :art: `:art:` when improving the format/structure of the code + * :racehorse: `:racehorse:` when improving performance + * :non-potable_water: `:non-potable_water:` when plugging memory leaks + * :memo: `:memo:` when writing docs + * :bug: `:bug:` when fixing a bug + * :fire: `:fire:` when removing code or files + * :green_heart: `:green_heart:` when fixing the CI build + * :white_check_mark: `:white_check_mark:` when adding tests + * :lock: `:lock:` when dealing with security + * :arrow_up: `:arrow_up:` when upgrading dependencies + * :arrow_down: `:arrow_down:` when downgrading dependencies + * :shirt: `:shirt:` when removing linter warnings ## Coding conventions Start reading our code and you'll get the hang of it. We optimize for readability: - * Configuration variables are uppercase and can be called in all modules keeping the same name. For example, MAVAR = getattr(settings, "MAVAR", default value) - * Global variables to a module are also in uppercase but are considered private to the module and therefore must be prefixed and suffixed with a double underscore - * All .py files must be indented using 4 spaces, and all other files (.css, .html, .js) with 2 spaces (soft tabs) - * This is open source software. Consider the people who will read your code, and make it look nice for them. It's sort of like driving a car: Perhaps you love doing donuts when you're alone, but with passengers the goal is to make the ride as smooth as possible. +* Configuration variables are uppercase and can be called +in all modules keeping the same name. +For example, MAVAR = getattr(settings, "MAVAR", default value) +* Global variables to a module are also in uppercase but are considered private +to the module and therefore must be prefixed and suffixed with a double underscore +* All .py files must be indented using 4 spaces, +and all other files (.css, .html, .js) with 2 spaces (soft tabs) +* This is open source software. +Consider the people who will read your code, and make it look nice for them. +It's sort of like driving a car: Perhaps you love doing donuts when you're alone, +but with passengers the goal is to make the ride as smooth as possible. ### JavaScript Styleguide @@ -156,12 +195,10 @@ Please use these typographic characters in all displayed strings: * Use Apostrophe (’) instead of single quote (') * English samples: don’t, it’s - * French samples : J’aime, l’histoire + * French samples: J’aime, l’histoire * Use the ellipsis (…) instead of 3 dots (...) * English sample: Loading… - * French sample : Chargement… + * French sample: Chargement… * Use typographic quotes (“ ”) instead of neutral quotes (" ") * English sample: You can use the “Description” field below. - * French sample : Utilisez le champ « Description » ci-dessous - - + * French sample: Utilisez le champ « Description » ci-dessous diff --git a/README.md b/README.md index 1a3eb9ef91..5f19ad65d4 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,48 @@ # Esup-Pod -[![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_main.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) [![Awesome CodeGouvFr score](https://img.shields.io/badge/awesome-codegouvfr_8/10-blue)](https://github.com/codegouvfr/awesome-codegouvfr#awesome-codegouvfr-score) +[![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_main.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) +[![Awesome CodeGouvFr score](https://img.shields.io/badge/awesome-codegouvfr_8/10-blue)](https://github.com/codegouvfr/awesome-codegouvfr#awesome-codegouvfr-score) -[FR] -## Plateforme de gestion de fichier vidéo +## [FR] -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/). +### Plateforme de gestion de fichier vidéo -Le projet et la plateforme qui porte le même nom ont pour but de faciliter la mise à disposition de vidéos et de ce fait, d’encourager l’utilisation de celles-ci dans le cadre de l’enseignement et la recherche. +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/). -### 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 technique") +Le projet et la plateforme qui porte le même nom ont pour but de faciliter +la mise à disposition de vidéos et de ce fait, d’encourager +l’utilisation de celles-ci dans le cadre de l’enseignement et la recherche. -### Documentation conteneurisation -* Accédez à toute la documentation concernant la conteneurisation (installation, paramétrage, lancement etc.) [dockerfile-dev-with-volumes/README.adoc](./dockerfile-dev-with-volumes/README.adoc "Documentation conteneurisation") +#### Documentation technique -### Documentation configuration de la plateforme -* Accédez à toute la documentation concernant la configuration (paramétrage, personnalisation etc.) [configuration](./CONFIGURATION_FR.md "Documentation configuration") +* [Documentation générale (installation, paramétrage etc.)](https://www.esup-portail.org/wiki/display/ES/esup-pod) +* [Conteneurisation (installation, paramétrage, lancement etc.)](./dockerfile-dev-with-volumes/README.adoc) +* [Configuration (paramétrage, personnalisation etc.)](./CONFIGURATION_FR.md) -[EN] -## Video file management platform -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 . +## [EN] -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. +### Video file management platform + +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 et de la Recherche +* The documentation (to install, customize, etc…) is on the + [ESUP Wiki](https://www.esup-portail.org/wiki/display/ES/esup-pod "Documentation") + +Université de Lille | Esup Portail | Ministère de lʼEnseignement supérieur et de la Recherche :-----:|:-----:|:----: diff --git a/SECURITY.md b/SECURITY.md index 6714221d7a..d37467500d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,8 +2,8 @@ ## Supported Versions -If you want most up to date secured version of Esup-Pod, we encourage you to upgrade to the last release. - +If you want most up to date secured version of Esup-Pod, +we encourage you to upgrade to the last release. ## Reporting a Vulnerability diff --git a/dockerfile-dev-with-volumes/README.adoc b/dockerfile-dev-with-volumes/README.adoc index 1bb70e6356..54290a9bd6 100755 --- a/dockerfile-dev-with-volumes/README.adoc +++ b/dockerfile-dev-with-volumes/README.adoc @@ -52,7 +52,7 @@ DJANGO_SUPERUSER_PASSWORD= DJANGO_SUPERUSER_EMAIL= ELASTICSEARCH_TAG=elasticsearch:8.8.1 NODE_TAG=node:19 -PYTHON_TAG=python:3.9-buster +PYTHON_TAG=python:3.9-bullseye REDIS_TAG=redis:alpine3.16 DOCKER_ENV=light ---- diff --git a/docs/index.md b/docs/index.md index d1d1347be1..d500ad7619 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,6 @@ # Plateforme Pod : Gérez vos vidéos -## Retrouvez toute la documentation à cette adresse : https://www.esup-portail.org/wiki/display/ES/esup-pod +**Retrouvez toute la documentation à cette adresse : [www.esup-portail.org/wiki](https://www.esup-portail.org/wiki/display/ES/esup-pod)** - -[](https://www.univ-lille.fr "Université de Lille") | [](https://www.esup-portail.org "Esup Portail") | [ Ministère de lʼEnseignement supérieur et de la Recherche](http://www.enseignementsup-recherche.gouv.fr "Ministère de lʼEnseignement supérieur et de la Recherche") +[Université de Lille](https://www.univ-lille.fr) | [Esup Portail](https://www.esup-portail.org) | [ Ministère de lʼEnseignement supérieur et de la Recherche](http://www.enseignementsup-recherche.gouv.fr) :-----:|:-----:|:----: diff --git a/pod/authentication/tests/test_utils.py b/pod/authentication/tests/test_utils.py index 84a1aeedd0..343958a28c 100644 --- a/pod/authentication/tests/test_utils.py +++ b/pod/authentication/tests/test_utils.py @@ -12,7 +12,7 @@ class UserTestUtils(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: self.admin = User.objects.create( first_name="pod", last_name="Admin", @@ -25,7 +25,7 @@ def setUp(self): ) print(" ---> SetUp of VideoTestFiltersViews: OK!") - def test_get_owners(self): + def test_get_owners(self) -> None: # Search with common word actual = get_owners("pod", 12, 0) expected = [ diff --git a/pod/bbb/management/commands/bbb.py b/pod/bbb/management/commands/bbb.py index 5ecc0ec2ac..567612002f 100644 --- a/pod/bbb/management/commands/bbb.py +++ b/pod/bbb/management/commands/bbb.py @@ -60,7 +60,7 @@ import dateutil.parser from django.core.mail import mail_admins from django.utils import timezone -from xml.dom import minidom +from defusedxml import minidom import urllib.parse from pod.video.models import Video, Type, get_storage_path_video diff --git a/pod/chapter/admin.py b/pod/chapter/admin.py index 7735a7a42f..92ce0e808a 100644 --- a/pod/chapter/admin.py +++ b/pod/chapter/admin.py @@ -1,3 +1,4 @@ +"""Esup-Pod Chapter administration.""" from django.contrib import admin from pod.chapter.models import Chapter from django.contrib.sites.shortcuts import get_current_site @@ -6,6 +7,8 @@ class ChapterAdmin(admin.ModelAdmin): + """Chapter administration.""" + list_display = ( "title", "video", diff --git a/pod/chapter/models.py b/pod/chapter/models.py index 1643bc3779..a7f21c160f 100644 --- a/pod/chapter/models.py +++ b/pod/chapter/models.py @@ -1,3 +1,4 @@ +"""Esup-Pod Video chapter models.""" import time from django.core.exceptions import ValidationError @@ -6,10 +7,13 @@ from django.utils.translation import ugettext as _ from pod.video.models import Video +from pod.video.utils import verify_field_length from pod.main.models import get_nextautoincrement class Chapter(models.Model): + """Chapter model.""" + video = models.ForeignKey(Video, verbose_name=_("video"), on_delete=models.CASCADE) title = models.CharField(_("title"), max_length=100) slug = models.SlugField( @@ -30,6 +34,8 @@ class Chapter(models.Model): ) class Meta: + """Chapter Metadata.""" + verbose_name = _("Chapter") verbose_name_plural = _("Chapters") ordering = ["time_start"] @@ -39,27 +45,15 @@ class Meta: "video", ) - def clean(self): + def clean(self) -> None: + """Check chapter fields validity.""" msg = list() - msg = self.verify_title_items() + self.verify_time() + msg = verify_field_length(self.title) + self.verify_time() msg = msg + self.verify_overlap() if len(msg) > 0: raise ValidationError(msg) - def verify_title_items(self): - msg = list() - if ( - not self.title - or self.title == "" - or len(self.title) < 2 - or len(self.title) > 100 - ): - msg.append(_("Please enter a title from 2 to 100 characters.")) - if len(msg) > 0: - return msg - return list() - - def verify_time(self): + def verify_time(self) -> list: """Check that start time is included inside video duration.""" msg = list() if ( @@ -73,11 +67,9 @@ def verify_time(self): + "{0}".format(self.video.duration - 1) ) ) - if len(msg) > 0: - return msg - return list() + return msg - def verify_overlap(self): + def verify_overlap(self) -> list: msg = list() instance = None if self.slug: @@ -95,11 +87,10 @@ def verify_overlap(self): + "end values." ).format(element.title) ) - if len(msg) > 0: - return msg - return list() + return msg - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: + """Save a chapter.""" newid = -1 if not self.id: try: @@ -117,14 +108,15 @@ def save(self, *args, **kwargs): super(Chapter, self).save(*args, **kwargs) @property - def chapter_in_time(self): + def chapter_in_time(self) -> str: return time.strftime("%H:%M:%S", time.gmtime(self.time_start)) chapter_in_time.fget.short_description = _("Start time") @property def sites(self): + """Get the related video sites.""" return self.video.sites - def __str__(self): + def __str__(self) -> str: return "Chapter: {0} - video: {1}".format(self.title, self.video) diff --git a/pod/chapter/static/js/chapters.js b/pod/chapter/static/js/chapters.js index 2b68050f7f..6258ff80b1 100644 --- a/pod/chapter/static/js/chapters.js +++ b/pod/chapter/static/js/chapters.js @@ -2,6 +2,18 @@ * @file Esup-Pod functions for chapter view. */ + +// Read-only globals defined in video_chapter.html +/* + global video_duration +*/ + +// Read-only globals defined in video-script.html +/* +global player +*/ + + var id_form = "form_chapter"; function show_form(data) { let form_chapter = document.getElementById(id_form); @@ -247,7 +259,7 @@ function verify_start_title_items() { inputTitle.value.length > 100 ) { if (typeof lengthErrorMsg === "undefined") { - lengthErrorMsg = document.createElement("div"); + let lengthErrorMsg = document.createElement("div"); lengthErrorMsg.id = errormsg_id; lengthErrorMsg.className = "invalid-feedback"; lengthErrorMsg.innerHTML = gettext( @@ -288,7 +300,7 @@ function verify_start_title_items() { inputStart.value >= video_duration ) { if (typeof timeErrorMsg === "undefined") { - timeErrorMsg = document.createElement("div"); + let timeErrorMsg = document.createElement("div"); timeErrorMsg.id = errormsg_id; timeErrorMsg.className = "invalid-feedback"; timeErrorMsg.innerHTML = @@ -317,6 +329,7 @@ function verify_start_title_items() { return ret; } +/** Unused function. TODO: delete in 3.7.0 function overlaptest() { var new_start = parseInt(document.getElementById("id_time_start").value); var id = parseInt(document.getElementById("id_chapter").value); @@ -337,6 +350,7 @@ function overlaptest() { }); return msg; } +*/ /*** Display element of form enrich ***/ Number.prototype.toHHMMSS = function () { @@ -386,7 +400,7 @@ document.addEventListener("click", (event) => { }); var updateDom = function (data) { - let player = window.videojs.players.podvideoplayer; + // let player = window.videojs.players.podvideoplayer; let n1 = document.getElementById("chapters"); let n2 = document.querySelector("div.chapters-list"); let tmp_node = document.createElement("div"); @@ -430,6 +444,7 @@ var manageDelete = function () { } }; +/** Unused function. TODO: delete in 3.7.0 var manageImport = function () { let player = window.videojs.players.podvideoplayer; let n = document.querySelector("div.chapters-list"); @@ -448,3 +463,4 @@ var manageImport = function () { } } }; +*/ diff --git a/pod/chapter/static/js/videojs-chapters.js b/pod/chapter/static/js/videojs-chapters.js index b3ebf4dbe8..b87710bd5a 100644 --- a/pod/chapter/static/js/videojs-chapters.js +++ b/pod/chapter/static/js/videojs-chapters.js @@ -61,7 +61,7 @@ constructor(player, options) { super(player, options); var settings = videojs.mergeOptions(defaults, options), - chapters = {}, + /*chapters = {},*/ currentChapter = document.createElement("li"); /** diff --git a/pod/chapter/tests/test_views.py b/pod/chapter/tests/test_views.py index 5040fa0d2d..3d67d16584 100644 --- a/pod/chapter/tests/test_views.py +++ b/pod/chapter/tests/test_views.py @@ -25,7 +25,7 @@ class ChapterViewsTestCase(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: site = Site.objects.get(id=1) owner = User.objects.create(username="test", password="azerty", is_staff=True) owner.set_password("hello") @@ -49,7 +49,7 @@ def setUp(self): vid.sites.add(site) - def test_video_chapter_owner(self): + def test_video_chapter_owner(self) -> None: video = Video.objects.get(id=1) url = reverse("video:chapter:video_chapter", kwargs={"slug": video.slug}) response = self.client.get(url) @@ -69,7 +69,7 @@ def test_video_chapter_owner(self): print(" ---> test_video_chapter_owner: OK!") print(" [ END CHAPTER VIEWS ] ") - def test_video_chapter(self): + def test_video_chapter(self) -> None: video = Video.objects.get(id=1) url = reverse("video:chapter:video_chapter", kwargs={"slug": video.slug}) response = self.client.get(url) @@ -86,7 +86,7 @@ def test_video_chapter(self): print(" [ BEGIN CHAPTER VIEWS ] ") print(" ---> test_video_chapter: OK!") - def test_video_chapter_new(self): + def test_video_chapter_new(self) -> None: video = Video.objects.get(id=1) authenticate(username="test", password="hello") login = self.client.login(username="test", password="hello") @@ -118,7 +118,7 @@ def test_video_chapter_new(self): print(" ---> test_video_chapter_new: OK!") - def test_video_chapter_edit(self): + def test_video_chapter_edit(self) -> None: video = Video.objects.get(id=1) url = reverse("video:chapter:video_chapter", kwargs={"slug": video.slug}) authenticate(username="test", password="hello") @@ -157,7 +157,7 @@ def test_video_chapter_edit(self): print(" ---> test_video_chapter_edit: OK!") - def test_video_chapter_delete(self): + def test_video_chapter_delete(self) -> None: video = Video.objects.get(id=1) authenticate(username="test", password="hello") login = self.client.login(username="test", password="hello") @@ -187,7 +187,8 @@ def test_video_chapter_delete(self): print(" ---> test_video_chapter_delete: OK!") - def test_video_chapter_import(self): + def test_video_chapter_import(self) -> None: + """Test Chapters creations from vtt file.""" video = Video.objects.get(id=1) authenticate(username="test", password="hello") login = self.client.login(username="test", password="hello") @@ -218,7 +219,7 @@ def test_video_chapter_import(self): print(" ---> test_video_chapter_import: OK!") - def test_video_chapter_modify(self): + def test_video_chapter_modify(self) -> None: video = Video.objects.get(id=1) Chapter.objects.create( title="Test Chapter", diff --git a/pod/chapter/utils.py b/pod/chapter/utils.py index 2a13e90003..55a83bad8d 100644 --- a/pod/chapter/utils.py +++ b/pod/chapter/utils.py @@ -1,3 +1,4 @@ +"""Esup-Pod Chapter utilities.""" import time import datetime @@ -5,7 +6,8 @@ from pod.chapter.models import Chapter -def vtt_to_chapter(vtt, video): +def vtt_to_chapter(vtt, video): # -> str | None: + """Convert a vtt file to Pod chapters.""" Chapter.objects.filter(video=video).delete() webvtt = WebVTT().read(vtt.file.path) for caption in webvtt: diff --git a/pod/chapter/views.py b/pod/chapter/views.py index 9e062764f3..f3d70ce654 100644 --- a/pod/chapter/views.py +++ b/pod/chapter/views.py @@ -1,3 +1,4 @@ +"""Esup-Pod Chapter views.""" from django.http import HttpResponse from django.http import HttpResponseForbidden from django.shortcuts import render diff --git a/pod/completion/admin.py b/pod/completion/admin.py index 6c12cc425f..12de1b4dbf 100644 --- a/pod/completion/admin.py +++ b/pod/completion/admin.py @@ -154,14 +154,14 @@ class EnrichModelQueueAdmin(admin.ModelAdmin): class TrackAdmin(admin.ModelAdmin): - def debug(text): + def debug(text) -> None: if DEBUG: print(text) def check_if_treatment_in_progress() -> bool: return EnrichModelQueue.objects.filter(in_treatment=True).exists() - def write_into_kaldi_file(enrich_model_queue: EnrichModelQueue): + def write_into_kaldi_file(enrich_model_queue: EnrichModelQueue) -> None: with open( MODEL_COMPILE_DIR + "/" + enrich_model_queue.lang + "/db/extra.txt", "w" ) as f: @@ -178,7 +178,7 @@ def write_into_kaldi_file(enrich_model_queue: EnrichModelQueue): ] ) - def copy_result_into_current_model(enrich_model_queue: EnrichModelQueue): + def copy_result_into_current_model(enrich_model_queue: EnrichModelQueue) -> None: from_path: str = ( MODEL_COMPILE_DIR + "/" + enrich_model_queue.lang + "/exp/chain/tdnn/graph" ) @@ -219,7 +219,8 @@ def copy_result_into_current_model(enrich_model_queue: EnrichModelQueue): if os.path.exists(from_path): shutil.copy(from_path, to_path) - def enrich_kaldi_model_launch(): + @staticmethod + def enrich_kaldi_model_launch() -> None: TrackAdmin.debug("enrich_kaldi_model") enrich_model_queue = EnrichModelQueue.objects.filter(model_type="VOSK").first() if enrich_model_queue is not None: @@ -239,7 +240,7 @@ def enrich_kaldi_model_launch(): return @admin.action(description=_("Enrich with selected subtitles")) - def enrich_model(modeladmin, request, queryset): + def enrich_model(modeladmin, request, queryset) -> None: text = "" title = "" for query in list(queryset.all()): diff --git a/pod/completion/models.py b/pod/completion/models.py index 089bdb56bd..b21176e866 100644 --- a/pod/completion/models.py +++ b/pod/completion/models.py @@ -1,3 +1,4 @@ +"""Esup-Pod video completion models.""" import base64 from django.db import models @@ -7,6 +8,7 @@ from django.template.defaultfilters import slugify from ckeditor.fields import RichTextField from pod.video.models import Video +from pod.video.utils import verify_field_length from pod.main.models import get_nextautoincrement from pod.main.lang_settings import ALL_LANG_CHOICES, PREF_LANG_CHOICES @@ -96,13 +98,14 @@ class Meta: def sites(self): return self.video.sites - def clean(self): + def clean(self) -> None: msg = list() msg = self.verify_attributs() + self.verify_not_same_contributor() if len(msg) > 0: raise ValidationError(msg) - def verify_attributs(self): + def verify_attributs(self) -> list: + """Validate contributor fields.""" msg = list() if not self.name or self.name == "": msg.append(_("Please enter a name.")) @@ -112,29 +115,25 @@ def verify_attributs(self): msg.append(_("You cannot enter a weblink with more than 200 caracters.")) if not self.role: msg.append(_("Please enter a role.")) - if len(msg) > 0: - return msg - else: - return list() + return msg - def verify_not_same_contributor(self): + def verify_not_same_contributor(self) -> list: + """Check that there is not already a contributor with same name+role.""" msg = list() list_contributor = Contributor.objects.filter(video=self.video) if self.id: list_contributor = list_contributor.exclude(id=self.id) - if len(list_contributor) > 0: - for element in list_contributor: - if self.name == element.name and self.role == element.role: - msg.append( - _( - "There is already a contributor with the same " - + "name and role in the list." - ) + for element in list_contributor: + if self.name == element.name and self.role == element.role: + msg.append( + _( + "There is already a contributor with the same " + + "name and role in the list." ) - return msg - return list() + ) + return msg - def __str__(self): + def __str__(self) -> str: return "Video:{0} - Name:{1} - Role:{2}".format(self.video, self.name, self.role) def get_base_mail(self) -> str: @@ -148,6 +147,8 @@ def get_noscript_mail(self) -> str: class Document(models.Model): + """Video additional documents.""" + video = models.ForeignKey(Video, verbose_name=_("Video"), on_delete=models.CASCADE) document = models.ForeignKey( CustomFileModel, @@ -163,6 +164,8 @@ class Document(models.Model): ) class Meta: + """Video additional document metadata.""" + verbose_name = _("Document") verbose_name_plural = _("Documents") @@ -170,19 +173,21 @@ class Meta: def sites(self): return self.video.sites - def clean(self): + def clean(self) -> None: msg = list() msg = self.verify_document() + self.verify_not_same_document() if len(msg) > 0: raise ValidationError(msg) - def verify_document(self): + def verify_document(self) -> list: + """Check that additional ressource include a file.""" msg = list() if not self.document: msg.append(_("Please enter a document.")) return msg - def verify_not_same_document(self): + def verify_not_same_document(self) -> list: + """Check if document is not already contained in the list.""" msg = list() list_doc = Document.objects.filter(video=self.video) if self.id: @@ -195,7 +200,7 @@ def verify_not_same_document(self): return msg return list() - def __str__(self): + def __str__(self) -> str: return "Document: {0} - Video: {1}".format(self.document.name, self.video) @@ -210,14 +215,18 @@ class EnrichModelQueue(models.Model): ) in_treatment = models.BooleanField(_("In Treatment"), default=False) - def get_label_lang(self): + def get_label_lang(self) -> str: + """Get translated label.""" return "%s" % __LANG_CHOICES_DICT__[self.lang] class Meta: - verbose_name = _("EnrichModelQueue") - verbose_name_plural = _("EnrichModelQueue") + """EnrichModelQueue Metadata.""" + + verbose_name = _("Enrich model queue") + verbose_name_plural = _("Enrich model queues") - def verify_attributs(self): + """ Unused function ? TODO: delete in 3.7.0 + def verify_attributs(self) -> list: msg = list() if not self.text: msg.append(_("Please enter a text.")) @@ -225,13 +234,13 @@ def verify_attributs(self): msg.append(_("Please enter a model type.")) if not self.lang: msg.append(_("Please enter a language.")) - if len(msg) > 0: - return msg - else: - return list() + return msg + """ class Track(models.Model): + """Video additional tracks (captions or subtitles).""" + video = models.ForeignKey(Video, verbose_name=_("Video"), on_delete=models.CASCADE) kind = models.CharField( _("Kind"), max_length=10, choices=KIND_CHOICES, default="subtitles" @@ -252,20 +261,24 @@ class Track(models.Model): def sites(self): return self.video.sites - def get_label_lang(self): + def get_label_lang(self) -> str: + """Get human readable label of additional track.""" return "%s" % __LANG_CHOICES_DICT__[self.lang] class Meta: + """Video additional tracks metadata.""" verbose_name = _("Track") verbose_name_plural = _("Tracks") - def clean(self): + def clean(self) -> None: + """Validate Track fields and eventually raise ValidationError.""" msg = list() msg = self.verify_attributs() + self.verify_not_same_track() if len(msg) > 0: raise ValidationError(msg) - def verify_attributs(self): + def verify_attributs(self) -> list: + """Validate Track fields.""" msg = list() if not self.kind: msg.append(_("Please enter a kind.")) @@ -277,33 +290,32 @@ def verify_attributs(self): msg.append(_("Please specify a track file.")) elif "vtt" not in self.src.file_type: msg.append(_("Only “.vtt” format is allowed.")) - if len(msg) > 0: - return msg - else: - return list() + return msg - def verify_not_same_track(self): + def verify_not_same_track(self) -> list: + """Check that there's not already a track with same kind & lang.""" msg = list() list_track = Track.objects.filter(video=self.video) if self.id: list_track = list_track.exclude(id=self.id) - if len(list_track) > 0: - for element in list_track: - if self.kind == element.kind and self.lang == element.lang: - msg.append( - _( - "There is already a subtitle with the " - + "same kind and language in the list." - ) + + for element in list_track: + if self.kind == element.kind and self.lang == element.lang: + msg.append( + _( + "There is already a subtitle with the " + + "same kind and language in the list." ) - return msg - return list() + ) + return msg - def __str__(self): + def __str__(self) -> str: return "{0} - File: {1} - Video: {2}".format(self.kind, self.src.name, self.video) class Overlay(models.Model): + """Video overlay.""" + POSITION_CHOICES = ( ("top-left", _("top-left")), ("top", _("top")), @@ -359,30 +371,21 @@ def sites(self): return self.video.sites class Meta: + """Video overlay metadata.""" + verbose_name = _("Overlay") verbose_name_plural = _("Overlays") ordering = ["time_start"] - def clean(self): + def clean(self) -> None: msg = list() - msg += self.verify_title_items() + msg += verify_field_length(self.title) msg += self.verify_time_items() msg += self.verify_overlap() if len(msg) > 0: raise ValidationError(msg) - def verify_title_items(self): - msg = list() - if not self.title or self.title == "": - msg.append(_("Please enter a title.")) - elif len(self.title) < 2 or len(self.title) > 100: - msg.append(_("Please enter a title from 2 to 100 characters.")) - if len(msg) > 0: - return msg - else: - return list() - - def verify_time_items(self): + def verify_time_items(self) -> list: msg = list() if self.time_start > self.time_end: msg.append( @@ -397,12 +400,9 @@ def verify_time_items(self): ) elif self.time_start == self.time_end: msg.append(_("Time end field and time start field can’t be equal.")) - if len(msg) > 0: - return msg - else: - return list() + return msg - def verify_overlap(self): + def verify_overlap(self) -> list: msg = list() instance = None if self.slug: @@ -429,7 +429,8 @@ def verify_overlap(self): return msg return list() - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: + """Store Overlay Object in db.""" newid = -1 if not self.id: try: @@ -446,5 +447,5 @@ def save(self, *args, **kwargs): self.slug = "{0}-{1}".format(newid, slugify(self.title)) super(Overlay, self).save(*args, **kwargs) - def __str__(self): + def __str__(self) -> str: return "Overlay: {0} - Video: {1}".format(self.title, self.video) diff --git a/pod/completion/templates/document/list_document.html b/pod/completion/templates/document/list_document.html index 6b84c0fd02..016d2dc9a3 100644 --- a/pod/completion/templates/document/list_document.html +++ b/pod/completion/templates/document/list_document.html @@ -14,11 +14,11 @@

{% trans 'List of additional resources' % {% for doc in list_document %} - {{doc.document.name}} ({{ doc.document.file_ext }}) - {% if doc.private %} - - {% else %} - + {{doc.document.name}} ({{ doc.document.file_ext }}) + {% if doc.private %} + + {% else %} + {% endif %} diff --git a/pod/completion/templates/video_caption_maker.html b/pod/completion/templates/video_caption_maker.html index 16132930a3..6cf12047ba 100644 --- a/pod/completion/templates/video_caption_maker.html +++ b/pod/completion/templates/video_caption_maker.html @@ -17,14 +17,10 @@ {% endblock %} -{% block page_title %} - {% trans "Video Caption Maker" %} -{% endblock %} - {% block page_content %}
@@ -209,11 +205,9 @@
diff --git a/pod/video/templates/videos/dublincore.html b/pod/video/templates/videos/dublincore.html index f9dd5b5ba6..4fd7ad681c 100644 --- a/pod/video/templates/videos/dublincore.html +++ b/pod/video/templates/videos/dublincore.html @@ -1,16 +1,10 @@ {% load video_filters %} -{% if xml == True %} - -{% endif %} +{% if xml == True %}{% endif %} {% for key, value in video.get_dublin_core.items %} {% if xml == True %} - <{{key}}> - {{value|metaformat|safe|striptags|truncatechars:300}} - + <{{key}}>{{value|metaformat|striptags|truncatechars:300}} {% else %} {% endif %} {% endfor %} -{% if xml == True %} - -{% endif %} +{% if xml == True %}{% endif %} diff --git a/pod/video/templates/videos/video-iframe.html b/pod/video/templates/videos/video-iframe.html index f1e74453c0..0a1d8dcd8a 100644 --- a/pod/video/templates/videos/video-iframe.html +++ b/pod/video/templates/videos/video-iframe.html @@ -30,7 +30,7 @@ {% if CSS_OVERRIDE %} {% endif %} - + diff --git a/pod/video/tests/test_feeds.py b/pod/video/tests/test_feeds.py index d771c89812..4d33424fff 100644 --- a/pod/video/tests/test_feeds.py +++ b/pod/video/tests/test_feeds.py @@ -5,7 +5,7 @@ from http import HTTPStatus from django.urls import reverse -from xml.dom import minidom +from defusedxml import minidom class FeedTestView(TestCase): diff --git a/pod/video/tests/test_models.py b/pod/video/tests/test_models.py index e7d39a7ff9..378c8b9226 100644 --- a/pod/video/tests/test_models.py +++ b/pod/video/tests/test_models.py @@ -1,4 +1,7 @@ -"""Video Models test cases.""" +"""Esup-Pod Video Models test cases. + +* run with 'python manage.py test pod.video.tests.test_models' +""" from django.test import TestCase from django.db.models import Count, Q @@ -51,7 +54,7 @@ class ChannelTestCase(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: """Create Channels to be tested.""" Channel.objects.create(title="ChannelTest1", slug="blabla") Channel.objects.create( @@ -64,7 +67,7 @@ def setUp(self): print(" ---> SetUp of ChannelTestCase: OK!") - def test_Channel_null_attribut(self): + def test_Channel_null_attribut(self) -> None: """ Test all channel attributs. @@ -88,7 +91,7 @@ def test_Channel_null_attribut(self): print(" ---> test_Channel_null_attribut of ChannelTestCase: OK!") - def test_Channel_with_attributs(self): + def test_Channel_with_attributs(self) -> None: """Test attributs when a channel has many attributs.""" channel = Channel.objects.annotate(video_count=Count("video", distinct=True)).get( title="ChannelTest2" @@ -106,7 +109,7 @@ def test_Channel_with_attributs(self): print(" ---> test_Channel_with_attributs of ChannelTestCase: OK!") - def test_delete_object(self): + def test_delete_object(self) -> None: """Test deleting Channels.""" Channel.objects.get(id=1).delete() Channel.objects.get(id=2).delete() @@ -122,7 +125,7 @@ class ThemeTestCase(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: """Create themes to be tested.""" Channel.objects.create(title="ChannelTest1") Theme.objects.create( @@ -138,7 +141,7 @@ def setUp(self): ) print(" ---> SetUp of ThemeTestCase: OK!") - def test_Theme_null_attribut(self): + def test_Theme_null_attribut(self) -> None: """ Test all attributs. @@ -158,14 +161,14 @@ def test_Theme_null_attribut(self): # + theme.slug + "/") print(" ---> test_Theme_null_attribut of ThemeTestCase: OK!") - def test_Theme_with_attributs(self): + def test_Theme_with_attributs(self) -> None: """Test attributs when a theme have many attributs.""" theme = Theme.objects.get(title="Theme1") theme.description = "blabla" self.assertEqual(theme.description, "blabla") print(" ---> test_Theme_with_attributs of ThemeTestCase: OK!") - def test_Theme_with_parent(self): + def test_Theme_with_parent(self) -> None: """Test attributs when a theme have many attributs.""" theme1 = Theme.objects.get(title="Theme1") theme2 = Theme.objects.get(title="Theme2") @@ -173,7 +176,7 @@ def test_Theme_with_parent(self): self.assertIn(theme2, theme1.children.all()) print(" ---> test_Theme_with_parent of ThemeTestCase: OK!") - def test_delete_object(self): + def test_delete_object(self) -> None: """Test delete object.""" Theme.objects.get(id=1).delete() self.assertEqual(Theme.objects.all().count(), 0) @@ -185,13 +188,13 @@ class TypeTestCase(TestCase): # fixtures = ['initial_data.json', ] - def setUp(self): + def setUp(self) -> None: """Create types to be tested.""" Type.objects.create(title="Type1") print(" ---> SetUp of TypeTestCase: OK!") - def test_Type_null_attribut(self): + def test_Type_null_attribut(self) -> None: """ Test all attributs. @@ -208,14 +211,14 @@ def test_Type_null_attribut(self): self.assertEqual(type1.description, _("-- sorry, no translation provided --")) print(" ---> test_Type_null_attribut of TypeTestCase: OK!") - def test_Type_with_attributs(self): + def test_Type_with_attributs(self) -> None: """Test attributs when a type have many attributs.""" type1 = Type.objects.get(title="Type1") type1.description = "blabla" self.assertEqual(type1.description, "blabla") print(" ---> test_Type_with_attributs of TypeTestCase: OK!") - def test_delete_object(self): + def test_delete_object(self) -> None: """Test delete object.""" Type.objects.get(id=1).delete() self.assertEquals(Type.objects.all().count(), 0) @@ -229,13 +232,13 @@ class DisciplineTestCase(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: """Create disciplines to be tested.""" Discipline.objects.create(title="Discipline1") print(" ---> SetUp of DisciplineTestCase: OK!") - def test_Discipline_null_attribut(self): + def test_Discipline_null_attribut(self) -> None: """ Test all attributs. @@ -254,14 +257,14 @@ def test_Discipline_null_attribut(self): ) print(" ---> test_Type_null_attribut of TypeTestCase: OK!") - def test_Discipline_with_attributs(self): + def test_Discipline_with_attributs(self) -> None: """Test attributs when a type has many attributs.""" discipline1 = Discipline.objects.get(title="Discipline1") discipline1.description = "blabla" self.assertEqual(discipline1.description, "blabla") print(" ---> test_Discipline_with_attributs of TypeTestCase: OK!") - def test_delete_object(self): + def test_delete_object(self) -> None: """Test delete object.""" Discipline.objects.get(id=1).delete() self.assertEqual(Discipline.objects.all().count(), 0) @@ -269,13 +272,16 @@ def test_delete_object(self): class VideoTestCase(TestCase): - """Test the Video and ViewCount.""" + """Test the Video and ViewCount. + + * Run with `python manage.py test pod.video.tests.test_models.VideoTestCase` + """ fixtures = [ "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: """Create videos to be tested.""" user = User.objects.create(username="pod", password="pod1234pod") @@ -324,7 +330,7 @@ def setUp(self): ViewCount.objects.create(video=video2, date=tomorrow, count=2) print(" ---> SetUp of VideoTestCase: OK!") - def test_last_Video_display(self): + def test_last_Video_display(self) -> None: filter_en = Video.objects.filter(encoding_in_progress=False, is_draft=False) filter_pass = filter_en.filter( Q(password="") | Q(password=None), is_restricted=False @@ -334,7 +340,7 @@ def test_last_Video_display(self): self.assertTrue(bool(filter_pass.filter(password__isnull=True))) print("---> test_last_Video_display of VideoTestCase: OK") - def test_Video_null_attributs(self): + def test_Video_null_attributs(self) -> None: video = Video.objects.get(id=1) self.assertEqual(video.video.name, "test.mp4") self.assertFalse(video.allow_downloading) @@ -359,7 +365,7 @@ def test_Video_null_attributs(self): print(" ---> test_Video_null_attributs of VideoTestCase: OK!") - def test_Video_many_attributs(self): + def test_Video_many_attributs(self) -> None: video2 = Video.objects.get(id=2) self.assertEqual(video2.video.name, get_storage_path_video(video2, "test.mp4")) self.assertTrue(video2.allow_downloading) @@ -375,7 +381,7 @@ def test_Video_many_attributs(self): print(" ---> test_Video_many_attributs of VideoTestCase: OK!") - def test_delete_object(self): + def test_delete_object(self) -> None: """Test deleting videos objects.""" for vid_id in [1, 2]: vid_object = Video.objects.get(id=vid_id) @@ -389,6 +395,32 @@ def test_delete_object(self): print(" ---> test_delete_object of Video: OK!") + def test_get_dublin_core(self) -> None: + """Test return of get_dublin_core function.""" + dc_vid = Video.objects.create( + title="Title containing special chars like `>&éè§çà€<`", + description="Desc containing HTML and special chars like `>&éè§çà€<`.", + video="test.mp4", + owner=User.objects.get(username="pod"), + type=Type.objects.get(id=1) + ) + + expected = { + "dc.title": "Title containing special chars like `>&éè§çà€<`", + "dc.description": "Desc containing <strong>HTML</strong> and special chars like <tt>`>&éè§çà€<`</tt>.", + "dc.type": "video", + "dc.format": "video/mp4" + } + got = dc_vid.get_dublin_core() + + # Check that generated DC looks like expected (with escaped entities). + self.assertEqual(expected["dc.title"], got["dc.title"]) + self.assertEqual(expected["dc.description"], got["dc.description"]) + self.assertEqual(expected["dc.type"], got["dc.type"]) + self.assertEqual(expected["dc.format"], got["dc.format"]) + + print(" ---> test_get_dublin_core of Video: OK!") + class VideoRenditionTestCase(TestCase): """Test the Video Rendition.""" @@ -403,7 +435,7 @@ def create_video_rendition( maxrate="2000k", audio_bitrate="300k", encode_mp4=False, - ): + ) -> VideoRendition: # print("create_video_rendition: %s" % resolution) return VideoRendition.objects.create( resolution=resolution, @@ -414,7 +446,7 @@ def create_video_rendition( encode_mp4=encode_mp4, ) - def test_VideoRendition_creation_by_default(self): + def test_VideoRendition_creation_by_default(self) -> None: vr = self.create_video_rendition() self.assertTrue(isinstance(vr, VideoRendition)) self.assertEqual( @@ -427,7 +459,7 @@ def test_VideoRendition_creation_by_default(self): VideoRenditionTestCase: OK!" ) - def test_VideoRendition_creation_with_values(self): + def test_VideoRendition_creation_with_values(self) -> None: # print("check resolution error") vr = self.create_video_rendition(resolution="totototo") self.assertTrue(isinstance(vr, VideoRendition)) @@ -478,7 +510,7 @@ def test_VideoRendition_creation_with_values(self): VideoRenditionTestCase: OK!" ) - def test_delete_object(self): + def test_delete_object(self) -> None: self.create_video_rendition(resolution="640x365") VideoRendition.objects.get(id=1).delete() self.assertEqual(VideoRendition.objects.all().count(), 0) @@ -491,7 +523,7 @@ class EncodingVideoTestCase(TestCase): # fixtures = ['initial_data.json', ] - def setUp(self): + def setUp(self) -> None: user = User.objects.create(username="pod", password="pod1234pod") Type.objects.create(title="test") Video.objects.create( @@ -508,7 +540,7 @@ def setUp(self): ) print(" ---> SetUp of EncodingVideoTestCase: OK!") - def test_EncodingVideo_null_attributs(self): + def test_EncodingVideo_null_attributs(self) -> None: ev = EncodingVideo.objects.create( video=Video.objects.get(id=1), rendition=VideoRendition.objects.get(resolution="640x360"), @@ -526,7 +558,7 @@ def test_EncodingVideo_null_attributs(self): ev.clean() print(" ---> SetUp of test_EncodingVideo_null_attributs: OK!") - def test_EncodingVideo_with_attributs(self): + def test_EncodingVideo_with_attributs(self) -> None: ev = EncodingVideo.objects.create( video=Video.objects.get(id=1), rendition=VideoRendition.objects.get(resolution="640x360"), @@ -546,7 +578,7 @@ def test_EncodingVideo_with_attributs(self): ev.clean() print(" ---> SetUp of test_EncodingVideo_with_attributs: OK!") - def test_EncodingVideo_with_false_attributs(self): + def test_EncodingVideo_with_false_attributs(self) -> None: ev = EncodingVideo.objects.create( video=Video.objects.get(id=1), rendition=VideoRendition.objects.get(resolution="640x360"), @@ -558,7 +590,7 @@ def test_EncodingVideo_with_false_attributs(self): self.assertRaises(ValidationError, ev.clean) print(" ---> SetUp of test_EncodingVideo_with_false_attributs: OK!") - def test_delete_object(self): + def test_delete_object(self) -> None: EncodingVideo.objects.create( video=Video.objects.get(id=1), rendition=VideoRendition.objects.get(resolution="640x360"), @@ -574,7 +606,7 @@ class EncodingAudioTestCase(TestCase): # fixtures = ['initial_data.json', ] - def setUp(self): + def setUp(self) -> None: user = User.objects.create(username="pod", password="pod1234pod") Type.objects.create(title="test") Video.objects.create( @@ -585,7 +617,7 @@ def setUp(self): ) print(" ---> SetUp of EncodingAudioTestCase: OK!") - def test_EncodingVideo_null_attributs(self): + def test_EncodingVideo_null_attributs(self) -> None: ea = EncodingAudio.objects.create(video=Video.objects.get(id=1)) self.assertTrue(isinstance(ea, EncodingAudio)) easlug = "EncodingAudio num: %s for video %s in %s" % ( @@ -600,7 +632,7 @@ def test_EncodingVideo_null_attributs(self): ea.clean() print(" ---> test_EncodingVideo_null_attributs: OK!") - def test_EncodingAudio_with_attributs(self): + def test_EncodingAudio_with_attributs(self) -> None: ea = EncodingAudio.objects.create( video=Video.objects.get(id=1), name="audio", @@ -619,7 +651,7 @@ def test_EncodingAudio_with_attributs(self): ea.clean() print(" ---> test_EncodingAudio_with_attributs: OK!") - def test_EncodingAudio_with_false_attributs(self): + def test_EncodingAudio_with_false_attributs(self) -> None: ea = EncodingAudio.objects.create( video=Video.objects.get(id=1), name="error", encoding_format="error" ) @@ -628,7 +660,7 @@ def test_EncodingAudio_with_false_attributs(self): self.assertRaises(ValidationError, ea.clean) print(" ---> test_EncodingAudio_with_false_attributs: OK!") - def test_delete_object(self): + def test_delete_object(self) -> None: EncodingAudio.objects.create(video=Video.objects.get(id=1)) EncodingAudio.objects.get(id=1).delete() self.assertEqual(EncodingAudio.objects.all().count(), 0) @@ -643,7 +675,7 @@ class PlaylistVideoTestCase(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: user = User.objects.create(username="pod", password="pod1234pod") Video.objects.create( title="Video1", @@ -653,7 +685,7 @@ def setUp(self): ) print(" ---> SetUp of PlaylistVideoTestCase: OK!") - def test_PlaylistVideo_null_attributs(self): + def test_PlaylistVideo_null_attributs(self) -> None: pv = PlaylistVideo.objects.create(video=Video.objects.get(id=1)) self.assertTrue(isinstance(pv, PlaylistVideo)) pvslug = "Playlist num: %s for video %s in %s" % ( @@ -668,7 +700,7 @@ def test_PlaylistVideo_null_attributs(self): pv.clean() print(" ---> test_PlaylistVideo_null_attributs: OK!") - def test_PlaylistVideo_with_attributs(self): + def test_PlaylistVideo_with_attributs(self) -> None: pv = PlaylistVideo.objects.create( video=Video.objects.get(id=1), name="audio", @@ -687,7 +719,7 @@ def test_PlaylistVideo_with_attributs(self): pv.clean() print(" ---> test_PlaylistVideo_with_attributs: OK!") - def test_PlaylistVideo_with_false_attributs(self): + def test_PlaylistVideo_with_false_attributs(self) -> None: pv = PlaylistVideo.objects.create( video=Video.objects.get(id=1), name="error", encoding_format="error" ) @@ -696,7 +728,7 @@ def test_PlaylistVideo_with_false_attributs(self): self.assertRaises(ValidationError, pv.clean) print(" ---> test_PlaylistVideo_with_false_attributs: OK!") - def test_delete_object(self): + def test_delete_object(self) -> None: PlaylistVideo.objects.create(video=Video.objects.get(id=1)) PlaylistVideo.objects.get(id=1).delete() self.assertEqual(PlaylistVideo.objects.all().count(), 0) @@ -711,7 +743,7 @@ class EncodingLogTestCase(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: user = User.objects.create(username="pod", password="pod1234pod") Video.objects.create( title="Video1", @@ -721,7 +753,7 @@ def setUp(self): ) print(" ---> SetUp of EncodingLogTestCase: OK!") - def test_EncodingLogTestCase_null_attributs(self): + def test_EncodingLogTestCase_null_attributs(self) -> None: el = EncodingLog.objects.create(video=Video.objects.get(id=1)) self.assertTrue(isinstance(el, EncodingLog)) elslug = "Log for encoding video %s" % (el.video.id) @@ -729,7 +761,7 @@ def test_EncodingLogTestCase_null_attributs(self): self.assertEqual(el.log, None) print(" ---> test_EncodingLogTestCase_null_attributs: OK!") - def test_EncodingLogTestCase_with_attributs(self): + def test_EncodingLogTestCase_with_attributs(self) -> None: el = EncodingLog.objects.create(video=Video.objects.get(id=1), log="encoding log") self.assertTrue(isinstance(el, EncodingLog)) elslug = "Log for encoding video %s" % (el.video.id) @@ -737,7 +769,7 @@ def test_EncodingLogTestCase_with_attributs(self): self.assertEqual(el.log, "encoding log") print(" ---> test_EncodingLogTestCase_with_attributs: OK!") - def test_delete_object(self): + def test_delete_object(self) -> None: EncodingLog.objects.create(video=Video.objects.get(id=1)) EncodingLog.objects.get(id=1).delete() self.assertEqual(EncodingLog.objects.all().count(), 0) @@ -752,7 +784,7 @@ class EncodingStepTestCase(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: user = User.objects.create(username="pod", password="pod1234pod") Video.objects.create( title="Video1", @@ -762,7 +794,7 @@ def setUp(self): ) print(" ---> SetUp of EncodingLogTestCase: OK!") - def test_EncodingStepTestCase_null_attributs(self): + def test_EncodingStepTestCase_null_attributs(self) -> None: es = EncodingStep.objects.create(video=Video.objects.get(id=1)) self.assertTrue(isinstance(es, EncodingStep)) esslug = "Step for encoding video %s" % (es.video.id) @@ -771,7 +803,7 @@ def test_EncodingStepTestCase_null_attributs(self): self.assertEqual(es.desc_step, None) print(" ---> test_EncodingStepTestCase_null_attributs: OK!") - def test_EncodingStepTestCase_with_attributs(self): + def test_EncodingStepTestCase_with_attributs(self) -> None: es = EncodingStep.objects.create( video=Video.objects.get(id=1), num_step=1, @@ -784,7 +816,7 @@ def test_EncodingStepTestCase_with_attributs(self): self.assertEqual(es.desc_step, "encoding step") print(" ---> test_EncodingStepTestCase_with_attributs: OK!") - def test_delete_object(self): + def test_delete_object(self) -> None: EncodingStep.objects.create(video=Video.objects.get(id=1)) EncodingStep.objects.get(id=1).delete() self.assertEqual(EncodingStep.objects.all().count(), 0) @@ -799,7 +831,7 @@ class NotesTestCase(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: user = User.objects.create(username="pod", password="pod1234pod") Video.objects.create( title="Video1", @@ -809,7 +841,7 @@ def setUp(self): ) print(" ---> SetUp of EncodingLogTestCase: OK!") - def test_NotesTestCase_null_attributs(self): + def test_NotesTestCase_null_attributs(self) -> None: note = Notes.objects.create( user=User.objects.get(username="pod"), video=Video.objects.get(id=1) ) @@ -829,7 +861,7 @@ def test_NotesTestCase_null_attributs(self): self.assertEqual(advnote.status, "0") print(" ---> test_NotesTestCase_null_attributs: OK!") - def test_NotesTestCase_with_attributs(self): + def test_NotesTestCase_with_attributs(self) -> None: note = Notes.objects.create( user=User.objects.get(username="pod"), video=Video.objects.get(id=1), @@ -855,7 +887,7 @@ def test_NotesTestCase_with_attributs(self): self.assertEqual(advnote.status, "1") print(" ---> test_NotesTestCase_with_attributs: OK!") - def test_delete_object(self): + def test_delete_object(self) -> None: Notes.objects.create( user=User.objects.get(username="pod"), video=Video.objects.get(id=1) ) @@ -877,7 +909,7 @@ class UserMarkerTimeTestCase(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: user = User.objects.create(username="pod", password="pod1234pod") Video.objects.create( title="Video1", @@ -887,7 +919,7 @@ def setUp(self): ) print(" ---> SetUp of UserMarkerTimeTestCase: OK!") - def test_create_UserMarkerTime_default(self): + def test_create_UserMarkerTime_default(self) -> None: user = User.objects.get(username="pod") video = Video.objects.get(id=1) markerTime = UserMarkerTime.objects.create(video=video, user=user) @@ -896,7 +928,7 @@ def test_create_UserMarkerTime_default(self): self.assertEqual(markerTime.markerTime, 0) print(" ---> test_create_UserMarkerTime_default: OK!") - def test_create_UserMarkerTime_with_attribut(self): + def test_create_UserMarkerTime_with_attribut(self) -> None: user = User.objects.get(username="pod") video = Video.objects.get(id=1) markerTime = UserMarkerTime(video=video, user=user, markerTime=60) @@ -908,7 +940,7 @@ def test_create_UserMarkerTime_with_attribut(self): self.assertEqual(markerTime.markerTime, 60) print(" ---> test_create_UserMarkerTime_with_attribut: OK!") - def test_create_UserMarkerTime_already_exist(self): + def test_create_UserMarkerTime_already_exist(self) -> None: user = User.objects.get(username="pod") video = Video.objects.get(id=1) UserMarkerTime.objects.create(video=video, user=user) @@ -922,7 +954,7 @@ def test_create_UserMarkerTime_already_exist(self): self.assertEqual(UserMarkerTime.objects.all().count(), 1) print(" ---> test_create_UserMarkerTime_already_exist: OK!") - def test_modify_UserMarkerTime(self): + def test_modify_UserMarkerTime(self) -> None: user = User.objects.get(username="pod") video = Video.objects.get(id=1) markerTime = UserMarkerTime(video=video, user=user, markerTime=60) @@ -934,7 +966,7 @@ def test_modify_UserMarkerTime(self): self.assertEqual(markerTime.markerTime, 120) print(" ---> test_modify_UserMarkerTime: OK!") - def test_delete_UserMarkerTime(self): + def test_delete_UserMarkerTime(self) -> None: user = User.objects.get(username="pod") video = Video.objects.get(id=1) markerTime = UserMarkerTime(video=video, user=user, markerTime=60) @@ -952,7 +984,7 @@ class VideoAccessTokenTestCase(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: user = User.objects.create(username="pod", password="pod1234pod") print("VIDEO: %s" % Video.objects.all().count()) self.video = Video.objects.create( @@ -964,7 +996,7 @@ def setUp(self): print("SET UP VIDEO ID: %s" % self.video.id) print(" ---> SetUp of VideoAccessTokenTestCase: OK!") - def test_create_VideoAccessToken_default(self): + def test_create_VideoAccessToken_default(self) -> None: """Test create default acces token for a video.""" accessToken = VideoAccessToken.objects.create(video=self.video) self.assertTrue(isinstance(accessToken, VideoAccessToken)) @@ -973,7 +1005,7 @@ def test_create_VideoAccessToken_default(self): self.assertNotEqual(accessToken.token, "") print(" ---> test_create_VideoAccessToken_default: OK!") - def test_create_VideoAccessToken_with_attribut(self): + def test_create_VideoAccessToken_with_attribut(self) -> None: """Test create access token with uuid for a video.""" uuid_test = uuid.uuid4() accessToken = VideoAccessToken(video=self.video, token=uuid_test) @@ -992,7 +1024,7 @@ def test_create_VideoAccessToken_with_attribut(self): self.assertEqual(VideoAccessToken.objects.all().count(), 1) print(" ---> test_create_VideoAccessToken_with_attribut: OK!") - def test_create_VideoAccessToken_already_exist(self): + def test_create_VideoAccessToken_already_exist(self) -> None: """Test unique access token for a video.""" uuid_test = uuid.uuid4() VideoAccessToken.objects.create(video=self.video, token=uuid_test) @@ -1006,7 +1038,7 @@ def test_create_VideoAccessToken_already_exist(self): self.assertEqual(VideoAccessToken.objects.all().count(), 1) print(" ---> test_create_VideoAccessToken_already_exist: OK!") - def test_delete_VideoAccessToken(self): + def test_delete_VideoAccessToken(self) -> None: """Test delete of access token for a video.""" accessToken = VideoAccessToken.objects.create(video=self.video) self.assertEqual(VideoAccessToken.objects.all().count(), 1) diff --git a/pod/video/utils.py b/pod/video/utils.py index c019ccde7e..b49359f362 100644 --- a/pod/video/utils.py +++ b/pod/video/utils.py @@ -287,3 +287,13 @@ def get_storage_path_video(instance, filename): instance.owner.owner.hashkey, "%s.%s" % (slugify(fname), extension), ) + + +def verify_field_length(field, field_name="title", max_length=100) -> list: + """Check field length, and return message.""" + msg = list() + if not field or field == "": + msg.append(_("Please enter a title.")) + elif len(field) < 2 or len(field) > max_length: + msg.append(_("Please enter a %s from 2 to %s characters." % (field_name, max_length))) + return msg diff --git a/pod/video/views.py b/pod/video/views.py index 1cf45ee8ae..2a449f5678 100644 --- a/pod/video/views.py +++ b/pod/video/views.py @@ -2118,7 +2118,7 @@ def video_note_download(request, slug): "content": [], } - def write_to_dict(t, id, s, rn, rc, dc, dm, nt, c): + def write_to_dict(t, id, s, rn, rc, dc, dm, nt, c) -> None: contentToDownload["type"].append(t) contentToDownload["id"].append(id) contentToDownload["status"].append(s) @@ -2129,7 +2129,7 @@ def write_to_dict(t, id, s, rn, rc, dc, dm, nt, c): contentToDownload["noteTimestamp"].append(nt) contentToDownload["content"].append(c) - def rec_expl_coms(idNote, lComs): + def rec_expl_coms(idNote, lComs) -> None: dictComs = get_com_coms_dict(request, lComs) for c in lComs: write_to_dict( diff --git a/pod/video_encode_transcript/tests/test_utils.py b/pod/video_encode_transcript/tests/test_utils.py index 494fc9d2e9..b28c35f00c 100644 --- a/pod/video_encode_transcript/tests/test_utils.py +++ b/pod/video_encode_transcript/tests/test_utils.py @@ -7,7 +7,7 @@ class EncodingUtilitiesTests(unittest.TestCase): """TestCase for Esup-Pod encoding utilities.""" - def test_dressing_position_value(self): + def test_dressing_position_value(self) -> None: """Test for the get_position_value function.""" result = get_dressing_position_value("top_right", "720") self.assertEqual(result, "overlay=main_w-overlay_w-36.0:36.0") diff --git a/pod/video_search/utils.py b/pod/video_search/utils.py index c07453792a..0e94bbac17 100644 --- a/pod/video_search/utils.py +++ b/pod/video_search/utils.py @@ -1,3 +1,4 @@ +"""Esup-Pod Video Search utilities.""" from django.conf import settings from elasticsearch import Elasticsearch from elasticsearch.exceptions import TransportError @@ -19,6 +20,7 @@ def index_es(video): + """Get ElasticSearch index.""" translation.activate(settings.LANGUAGE_CODE) es = Elasticsearch( ES_URL, @@ -53,7 +55,7 @@ def index_es(video): def delete_es(video): - """Delete an Elasticsearch entry.""" + """Delete an Elasticsearch video entry.""" es = Elasticsearch( ES_URL, timeout=ES_TIMEOUT, @@ -86,6 +88,7 @@ def delete_es(video): def create_index_es(): + """Create ElasticSearch index.""" es = Elasticsearch( ES_URL, timeout=ES_TIMEOUT, @@ -116,6 +119,7 @@ def create_index_es(): def delete_index_es(): + """Delete ElasticSearch index.""" es = Elasticsearch( ES_URL, timeout=ES_TIMEOUT, diff --git a/requirements.txt b/requirements.txt index 61e146e046..67ea6e535d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,3 +38,4 @@ paramiko~=3.1.0 djangorestframework-simplejwt==5.3.0 django-pwa==1.1.0 django-webpush==0.3.5 +defusedxml==0.7.1 diff --git a/setup.cfg b/setup.cfg index 61f206648d..a83b43d819 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,14 @@ exclude = .git,pod/*/migrations/*.py,test_settings.py,node_modules/*/*.py max-complexity = 7 max-line-length = 90 +# See https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#flake8 ignore = + # E501: line too long (will be auto corrected by Black) + E501, + # E203: Whitespace before ':' + # cf Black Readme: "E203 is not PEP 8 compliant" + E203, + # W503: line break before binary operator W503,