]> git.parisson.com Git - teleforma.git/commitdiff
Generate pdf with annotations for scripts
authorYoan Le Clanche <yoanl@pilotsystems.net>
Tue, 4 Jun 2024 13:32:21 +0000 (15:32 +0200)
committerYoan Le Clanche <yoanl@pilotsystems.net>
Tue, 4 Jun 2024 13:32:21 +0000 (15:32 +0200)
poetry.lock
teleforma/exam/templates/exam/script_detail.html
teleforma/exam/urls.py
teleforma/exam/views.py

index f60c87e2fd06f1c0a81aa69483b63594c1eaf75c..00a7c44f98997faa093e8e2781188b256e9161ce 100644 (file)
@@ -1,10 +1,9 @@
-# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
 
 [[package]]
 name = "aioredis"
 version = "1.3.1"
 description = "asyncio (PEP 3156) Redis support"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -20,7 +19,6 @@ hiredis = "*"
 name = "anyio"
 version = "4.3.0"
 description = "High level compatibility layer for multiple asynchronous event loop implementations"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -43,7 +41,6 @@ trio = ["trio (>=0.23)"]
 name = "asgiref"
 version = "3.8.1"
 description = "ASGI specs, helper code, and adapters"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -61,7 +58,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
 name = "asttokens"
 version = "2.4.1"
 description = "Annotate AST trees with source code positions"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -80,7 +76,6 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"]
 name = "async-timeout"
 version = "4.0.3"
 description = "Timeout context manager for asyncio programs"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -92,7 +87,6 @@ files = [
 name = "attrs"
 version = "23.2.0"
 description = "Classes Without Boilerplate"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -112,7 +106,6 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p
 name = "autobahn"
 version = "23.6.2"
 description = "WebSocket client & server library, WAMP real-time framework"
-category = "main"
 optional = false
 python-versions = ">=3.9"
 files = [
@@ -141,7 +134,6 @@ xbr = ["base58 (>=2.1.0)", "bitarray (>=2.7.5)", "cbor2 (>=5.2.0)", "click (>=8.
 name = "automat"
 version = "22.10.0"
 description = "Self-service finite-state machines for the programmer on the go."
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -160,7 +152,6 @@ visualize = ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"]
 name = "bigbluebutton-api-python"
 version = "0.0.11"
 description = "Python library that provides access to the API of BigBlueButton"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -176,7 +167,6 @@ jxmlease = "*"
 name = "boto3"
 version = "1.34.91"
 description = "The AWS SDK for Python"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -196,7 +186,6 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
 name = "botocore"
 version = "1.34.91"
 description = "Low-level, data-driven core of boto 3."
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -216,7 +205,6 @@ crt = ["awscrt (==0.20.9)"]
 name = "cairocffi"
 version = "1.6.1"
 description = "cffi-based cairo bindings for Python"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -236,7 +224,6 @@ xcb = ["xcffib (>=1.4.0)"]
 name = "cairosvg"
 version = "2.7.1"
 description = "A Simple SVG Converter based on Cairo"
-category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -259,7 +246,6 @@ test = ["flake8", "isort", "pytest"]
 name = "certifi"
 version = "2024.2.2"
 description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -271,7 +257,6 @@ files = [
 name = "cffi"
 version = "1.16.0"
 description = "Foreign Function Interface for Python calling C code."
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -336,7 +321,6 @@ pycparser = "*"
 name = "channels"
 version = "3.0.4"
 description = "Brings async, event-driven capabilities to Django. Django 2.2 and up only."
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -356,7 +340,6 @@ tests = ["async-timeout", "coverage (>=4.5,<5.0)", "pytest", "pytest-asyncio", "
 name = "channels-redis"
 version = "3.4.0"
 description = "Redis-backed ASGI channel layer implementation"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -374,11 +357,21 @@ msgpack = ">=1.0,<2.0"
 cryptography = ["cryptography (>=1.3.0)"]
 tests = ["async-generator", "async-timeout", "cryptography (>=1.3.0)", "pytest", "pytest-asyncio (==0.14.0)"]
 
+[[package]]
+name = "chardet"
+version = "5.2.0"
+description = "Universal encoding detector for Python 3"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"},
+    {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"},
+]
+
 [[package]]
 name = "charset-normalizer"
 version = "3.3.2"
 description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "main"
 optional = false
 python-versions = ">=3.7.0"
 files = [
@@ -478,7 +471,6 @@ files = [
 name = "click"
 version = "8.1.7"
 description = "Composable command line interface toolkit"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -493,7 +485,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
 name = "colorama"
 version = "0.4.6"
 description = "Cross-platform colored terminal text."
-category = "main"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
 files = [
@@ -505,7 +496,6 @@ files = [
 name = "constantly"
 version = "23.10.4"
 description = "Symbolic constants in Python"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -517,7 +507,6 @@ files = [
 name = "cryptography"
 version = "42.0.5"
 description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -572,7 +561,6 @@ test-randomorder = ["pytest-randomly"]
 name = "cssselect2"
 version = "0.7.0"
 description = "CSS selectors for Python ElementTree"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -592,7 +580,6 @@ test = ["flake8", "isort", "pytest"]
 name = "daphne"
 version = "3.0.2"
 description = "Django ASGI (HTTP/WebSocket) server"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -612,7 +599,6 @@ tests = ["hypothesis (==4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<
 name = "decorator"
 version = "5.1.1"
 description = "Decorators for Humans"
-category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -624,7 +610,6 @@ files = [
 name = "defusedxml"
 version = "0.7.1"
 description = "XML bomb protection for Python stdlib modules"
-category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
@@ -636,7 +621,6 @@ files = [
 name = "dj-pagination"
 version = "2.5.0"
 description = "Django + Pagination Made Easy"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -648,7 +632,6 @@ files = [
 name = "django"
 version = "3.2.25"
 description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -669,7 +652,6 @@ bcrypt = ["bcrypt"]
 name = "django-debug-toolbar"
 version = "3.2.1"
 description = "A configurable set of panels that display various debug information about the current request/response."
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -685,7 +667,6 @@ sqlparse = ">=0.2.0"
 name = "django-jazzmin"
 version = "2.4.7"
 description = "Drop-in theme for django admin, that utilises AdminLTE 3 & Bootstrap 4 to make yo' admin look jazzy"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -700,7 +681,6 @@ django = ">=2"
 name = "django-json-rpc"
 version = "0.7.1"
 description = "A simple JSON-RPC implementation for Django"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -715,7 +695,6 @@ six = "*"
 name = "django-nvd3"
 version = "0.9.7"
 description = "Django NVD3 - Chart Library for d3.js"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -729,7 +708,6 @@ python-nvd3 = "0.14.2"
 name = "django-postman"
 version = "4.2"
 description = "User-to-User messaging system for Django, with gateway to AnonymousUser, moderation and thread management, user & exchange filters, inbox/sent/archives/trash folders, support for apps: auto-complete, notification, mailer."
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -743,7 +721,6 @@ Django = "*"
 name = "django-recaptcha"
 version = "2.0.6"
 description = "Django recaptcha form field/widget app."
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -757,7 +734,6 @@ django = ">1.11,<4.0"
 name = "django-storages"
 version = "1.14.2"
 description = "Support for many storage backends in Django"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -782,7 +758,6 @@ sftp = ["paramiko (>=1.15)"]
 name = "django-tinymce"
 version = "3.3.0"
 description = "A Django application that contains a widget to render a form field as a TinyMCE editor."
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -794,7 +769,6 @@ files = [
 name = "django-unique-session"
 version = "1.0"
 description = "Unique session handler for Django"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -806,7 +780,6 @@ files = [
 name = "django-user-agents"
 version = "0.4.0"
 description = "A django package that allows easy identification of visitors' browser, operating system and device information (mobile phone, tablet or has touch capabilities)."
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -822,7 +795,6 @@ user-agents = "*"
 name = "djangorestframework"
 version = "3.13.1"
 description = "Web APIs for Django, made easy."
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -838,7 +810,6 @@ pytz = "*"
 name = "docutils"
 version = "0.17.1"
 description = "Docutils -- Python Documentation Utilities"
-category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
@@ -850,7 +821,6 @@ files = [
 name = "exceptiongroup"
 version = "1.2.1"
 description = "Backport of PEP 654 (exception groups)"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -865,7 +835,6 @@ test = ["pytest (>=6)"]
 name = "executing"
 version = "2.0.1"
 description = "Get the currently executing AST node of a frame, and other information"
-category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -880,7 +849,6 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth
 name = "h11"
 version = "0.14.0"
 description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -892,7 +860,6 @@ files = [
 name = "hiredis"
 version = "2.3.2"
 description = "Python wrapper for hiredis"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1011,7 +978,6 @@ files = [
 name = "html5lib"
 version = "1.1"
 description = "HTML parser based on the WHATWG HTML specification"
-category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
@@ -1033,7 +999,6 @@ lxml = ["lxml"]
 name = "httpcore"
 version = "0.16.3"
 description = "A minimal low-level HTTP client."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1045,17 +1010,16 @@ files = [
 anyio = ">=3.0,<5.0"
 certifi = "*"
 h11 = ">=0.13,<0.15"
-sniffio = ">=1.0.0,<2.0.0"
+sniffio = "==1.*"
 
 [package.extras]
 http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
+socks = ["socksio (==1.*)"]
 
 [[package]]
 name = "httptools"
 version = "0.6.1"
 description = "A collection of framework independent HTTP protocol utils."
-category = "main"
 optional = false
 python-versions = ">=3.8.0"
 files = [
@@ -1104,7 +1068,6 @@ test = ["Cython (>=0.29.24,<0.30.0)"]
 name = "httpx"
 version = "0.23.3"
 description = "The next generation HTTP client."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1120,15 +1083,14 @@ sniffio = "*"
 
 [package.extras]
 brotli = ["brotli", "brotlicffi"]
-cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"]
+cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"]
 http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
+socks = ["socksio (==1.*)"]
 
 [[package]]
 name = "hyperlink"
 version = "21.0.0"
 description = "A featureful, immutable, and correct URL for Python."
-category = "main"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -1143,7 +1105,6 @@ idna = ">=2.5"
 name = "idna"
 version = "3.7"
 description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -1155,7 +1116,6 @@ files = [
 name = "incremental"
 version = "22.10.0"
 description = "\"A small library that versions your Python projects.\""
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1171,7 +1131,6 @@ scripts = ["click (>=6.0)", "twisted (>=16.4.0)"]
 name = "ipython"
 version = "8.23.0"
 description = "IPython: Productive Interactive Computing"
-category = "main"
 optional = false
 python-versions = ">=3.10"
 files = [
@@ -1210,7 +1169,6 @@ test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "num
 name = "jedi"
 version = "0.19.1"
 description = "An autocompletion tool for Python that can be used for text editors."
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1230,7 +1188,6 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
 name = "jinja2"
 version = "3.1.3"
 description = "A very fast and expressive template engine."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1248,7 +1205,6 @@ i18n = ["Babel (>=2.7)"]
 name = "jmespath"
 version = "1.0.1"
 description = "JSON Matching Expressions"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1260,7 +1216,6 @@ files = [
 name = "jxmlease"
 version = "1.0.3"
 description = "jxmlease converts between XML and intelligent Python data structures."
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1272,7 +1227,6 @@ files = [
 name = "markupsafe"
 version = "2.1.5"
 description = "Safely add untrusted strings to HTML/XML markup."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1342,7 +1296,6 @@ files = [
 name = "matplotlib-inline"
 version = "0.1.7"
 description = "Inline Matplotlib backend for Jupyter"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1357,7 +1310,6 @@ traitlets = "*"
 name = "msgpack"
 version = "1.0.8"
 description = "MessagePack serializer"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1423,7 +1375,6 @@ files = [
 name = "numpy"
 version = "1.26.4"
 description = "Fundamental package for array computing in Python"
-category = "main"
 optional = false
 python-versions = ">=3.9"
 files = [
@@ -1469,7 +1420,6 @@ files = [
 name = "parso"
 version = "0.8.4"
 description = "A Python Parser"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1485,7 +1435,6 @@ testing = ["docopt", "pytest"]
 name = "pexpect"
 version = "4.9.0"
 description = "Pexpect allows easy control of interactive console applications."
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1500,7 +1449,6 @@ ptyprocess = ">=0.5"
 name = "pillow"
 version = "10.3.0"
 description = "Python Imaging Library (Fork)"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1587,7 +1535,6 @@ xmp = ["defusedxml"]
 name = "prompt-toolkit"
 version = "3.0.43"
 description = "Library for building powerful interactive command lines in Python"
-category = "main"
 optional = false
 python-versions = ">=3.7.0"
 files = [
@@ -1602,7 +1549,6 @@ wcwidth = "*"
 name = "psycopg2"
 version = "2.8.6"
 description = "psycopg2 - Python-PostgreSQL Database Adapter"
-category = "main"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
 files = [
@@ -1627,7 +1573,6 @@ files = [
 name = "ptyprocess"
 version = "0.7.0"
 description = "Run a subprocess in a pseudo terminal"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1639,7 +1584,6 @@ files = [
 name = "pure-eval"
 version = "0.2.2"
 description = "Safely evaluate AST nodes without side effects"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1654,7 +1598,6 @@ tests = ["pytest"]
 name = "pyasn1"
 version = "0.6.0"
 description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1666,7 +1609,6 @@ files = [
 name = "pyasn1-modules"
 version = "0.4.0"
 description = "A collection of ASN.1-based protocols modules"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1681,7 +1623,6 @@ pyasn1 = ">=0.4.6,<0.7.0"
 name = "pycparser"
 version = "2.22"
 description = "C parser in Python"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1693,7 +1634,6 @@ files = [
 name = "pygments"
 version = "2.17.2"
 description = "Pygments is a syntax highlighting package written in Python."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1709,7 +1649,6 @@ windows-terminal = ["colorama (>=0.4.6)"]
 name = "pymemcache"
 version = "3.4.4"
 description = "A comprehensive, fast, pure Python memcached client"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1724,7 +1663,6 @@ six = "*"
 name = "pyopenssl"
 version = "24.1.0"
 description = "Python wrapper module around the OpenSSL library"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1743,7 +1681,6 @@ test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"]
 name = "pypdf"
 version = "4.2.0"
 description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1765,7 +1702,6 @@ image = ["Pillow (>=8.0.0)"]
 name = "pyphen"
 version = "0.15.0"
 description = "Pure Python module to hyphenate text"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1781,7 +1717,6 @@ test = ["pytest", "ruff"]
 name = "python-dateutil"
 version = "2.9.0.post0"
 description = "Extensions to the standard Python datetime module"
-category = "main"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
 files = [
@@ -1796,7 +1731,6 @@ six = ">=1.5"
 name = "python-dotenv"
 version = "1.0.1"
 description = "Read key-value pairs from a .env file and set them as environment variables"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1811,7 +1745,6 @@ cli = ["click (>=5.0)"]
 name = "python-nvd3"
 version = "0.14.2"
 description = "Python NVD3 - Chart Library for d3.js"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1826,7 +1759,6 @@ python-slugify = "1.1.4"
 name = "python-slugify"
 version = "1.1.4"
 description = "A Python Slugify application that handles Unicode"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1840,7 +1772,6 @@ Unidecode = ">=0.04.16"
 name = "pytz"
 version = "2024.1"
 description = "World timezone definitions, modern and historical"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1852,7 +1783,6 @@ files = [
 name = "pyyaml"
 version = "6.0.1"
 description = "YAML parser and emitter for Python"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1913,7 +1843,6 @@ files = [
 name = "redis"
 version = "3.5.3"
 description = "Python client for Redis key-value store"
-category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 files = [
@@ -1924,11 +1853,30 @@ files = [
 [package.extras]
 hiredis = ["hiredis (>=0.1.3)"]
 
+[[package]]
+name = "reportlab"
+version = "4.2.0"
+description = "The Reportlab Toolkit"
+optional = false
+python-versions = "<4,>=3.7"
+files = [
+    {file = "reportlab-4.2.0-py3-none-any.whl", hash = "sha256:53630f9d25a7938def3e6a93d723b72a7a5921d34d23cf7a0930adeb2cb0e6c1"},
+    {file = "reportlab-4.2.0.tar.gz", hash = "sha256:474fb28d63431a5d47d75c90d580393050df7d491a09c7877df3291a2e9f6d0a"},
+]
+
+[package.dependencies]
+chardet = "*"
+pillow = ">=9.0.0"
+
+[package.extras]
+accel = ["rl-accel (>=0.9.0,<1.1)"]
+pycairo = ["freetype-py (>=2.3.0,<2.4)", "rlPyCairo (>=0.2.0,<1)"]
+renderpm = ["rl-renderPM (>=4.0.3,<4.1)"]
+
 [[package]]
 name = "requests"
 version = "2.31.0"
 description = "Python HTTP for Humans."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1950,7 +1898,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
 name = "rfc3986"
 version = "1.5.0"
 description = "Validating URI References per RFC 3986"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -1968,7 +1915,6 @@ idna2008 = ["idna"]
 name = "s3transfer"
 version = "0.10.1"
 description = "An Amazon S3 Transfer Manager"
-category = "main"
 optional = false
 python-versions = ">= 3.8"
 files = [
@@ -1986,7 +1932,6 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"]
 name = "service-identity"
 version = "24.1.0"
 description = "Service identity verification for pyOpenSSL & cryptography."
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2011,7 +1956,6 @@ tests = ["coverage[toml] (>=5.0.2)", "pytest"]
 name = "setuptools"
 version = "69.5.1"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2028,7 +1972,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar
 name = "six"
 version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
-category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 files = [
@@ -2040,7 +1983,6 @@ files = [
 name = "sniffio"
 version = "1.3.1"
 description = "Sniff out which async library your code is running under"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2052,7 +1994,6 @@ files = [
 name = "sorl-thumbnail"
 version = "12.10.0"
 description = "Thumbnails for Django"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2064,7 +2005,6 @@ files = [
 name = "sqlparse"
 version = "0.5.0"
 description = "A non-validating SQL parser."
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2080,7 +2020,6 @@ doc = ["sphinx"]
 name = "stack-data"
 version = "0.6.3"
 description = "Extract data from python stack frames and tracebacks for informative displays"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2100,7 +2039,6 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
 name = "tinycss2"
 version = "1.3.0"
 description = "A tiny CSS parser"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2119,7 +2057,6 @@ test = ["pytest", "ruff"]
 name = "traitlets"
 version = "5.14.3"
 description = "Traitlets Python configuration system"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2135,7 +2072,6 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,
 name = "twisted"
 version = "24.3.0"
 description = "An asynchronous networking framework written in Python"
-category = "main"
 optional = false
 python-versions = ">=3.8.0"
 files = [
@@ -2175,7 +2111,6 @@ windows-platform = ["pywin32 (!=226)", "pywin32 (!=226)", "twisted[all-non-platf
 name = "twisted-iocpsupport"
 version = "1.0.4"
 description = "An extension for use in the twisted I/O Completion Ports reactor."
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2204,7 +2139,6 @@ files = [
 name = "txaio"
 version = "23.1.1"
 description = "Compatibility API between asyncio/Twisted/Trollius"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2221,7 +2155,6 @@ twisted = ["twisted (>=20.3.0)", "zope.interface (>=5.2.0)"]
 name = "typing-extensions"
 version = "4.11.0"
 description = "Backported and Experimental Type Hints for Python 3.8+"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2233,7 +2166,6 @@ files = [
 name = "ua-parser"
 version = "0.18.0"
 description = "Python port of Browserscope's user agent parser"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2245,7 +2177,6 @@ files = [
 name = "unidecode"
 version = "1.2.0"
 description = "ASCII transliterations of Unicode text"
-category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -2257,7 +2188,6 @@ files = [
 name = "urllib3"
 version = "2.2.1"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2275,7 +2205,6 @@ zstd = ["zstandard (>=0.18.0)"]
 name = "user-agents"
 version = "2.2.0"
 description = "A library to identify devices (phones, tablets) and their capabilities by parsing browser user agent strings."
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2290,7 +2219,6 @@ ua-parser = ">=0.10.0"
 name = "uvicorn"
 version = "0.18.1"
 description = "The lightning-fast ASGI server."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2305,7 +2233,7 @@ h11 = ">=0.8"
 httptools = {version = ">=0.4.0", optional = true, markers = "extra == \"standard\""}
 python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
 PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
-uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
+uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
 watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
 websockets = {version = ">=10.0", optional = true, markers = "extra == \"standard\""}
 
@@ -2316,7 +2244,6 @@ standard = ["PyYAML (>=5.1)", "colorama (>=0.4)", "httptools (>=0.4.0)", "python
 name = "uvloop"
 version = "0.19.0"
 description = "Fast implementation of asyncio event loop on top of libuv"
-category = "main"
 optional = false
 python-versions = ">=3.8.0"
 files = [
@@ -2361,7 +2288,6 @@ test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)"
 name = "uwsgi"
 version = "2.0.25.1"
 description = "The uWSGI server"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2372,7 +2298,6 @@ files = [
 name = "watchfiles"
 version = "0.21.0"
 description = "Simple, modern and high performance file watching and code reload in python."
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2460,7 +2385,6 @@ anyio = ">=3.0.0"
 name = "wcwidth"
 version = "0.2.13"
 description = "Measures the displayed width of unicode strings in a terminal"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2472,7 +2396,6 @@ files = [
 name = "weasyprint"
 version = "52.5"
 description = "The Awesome Document Factory"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -2499,7 +2422,6 @@ test = ["pytest-cov", "pytest-flake8", "pytest-isort", "pytest-runner"]
 name = "webencodings"
 version = "0.5.1"
 description = "Character encoding aliases for legacy web content"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2511,7 +2433,6 @@ files = [
 name = "websockets"
 version = "12.0"
 description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2593,7 +2514,6 @@ files = [
 name = "xlrd"
 version = "2.0.1"
 description = "Library for developers to extract data from Microsoft Excel (tm) .xls spreadsheet files"
-category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
 files = [
@@ -2610,7 +2530,6 @@ test = ["pytest", "pytest-cov"]
 name = "xlwt"
 version = "1.3.0"
 description = "Library to create spreadsheet files compatible with MS Excel 97/2000/XP/2003 XLS files, on any platform, with Python 2.6, 2.7, 3.3+"
-category = "main"
 optional = false
 python-versions = "*"
 files = [
@@ -2622,7 +2541,6 @@ files = [
 name = "zope-interface"
 version = "6.3"
 description = "Interfaces for Python"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2675,4 +2593,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.10"
-content-hash = "ecf8b0f59566e224cb558aacffda4f46926866a8d8471898ff1b032d0ab7009d"
+content-hash = "6bc6cbf6fbe9ed773cacf9451ece9f8e29997e5426ad0dddb40529ea90a7bf3c"
index f7d5dadb0d8b4f8802cb09fc20af903cd373fc03..48e9f4de604b808b67ede7788dd04cec72eb5858 100644 (file)
 <div class="course viewer">
 
 <div class="course_title">
-
+    
     <a href="{% url 'teleforma-desk-period-course' period.id script.course.id %}">{{ script.title }}</a>
 
     <div style="float: right; font-size: 0.9em;">
+        <a href="{% url 'teleforma-exam-script-download' period.id script.id %}" class="component_icon button icon_download">Télécharger</a>
         {% if user.quotas.all %}
             <a href="{% url 'postman:write' script.author.username %}" class="component_icon button icon_next">{% trans "Send a message" %}</a>
         {% endif %}
index ddf3aab615278b5ca2c5e90aa69dedbb5e9e1f75..8140c1e84bfccf72bb6ea7d1b1fb0068925f9f67 100644 (file)
 #
 # Authors: Guillaume Pellerin <yomguy@parisson.com>
 
-from teleforma.exam.views import MassScoreCreateView, ScoreCreateView, ScriptCreateView, ScriptView, ScriptsPendingView, ScriptsRejectedView, ScriptsScoreAllView, ScriptsScoreCourseView, ScriptsTreatedView, ScriptsView, get_correctors, get_mass_students, QuotasView
+from teleforma.exam.views import MassScoreCreateView, ScoreCreateView, ScriptCreateView, ScriptDownloadView, ScriptView, ScriptsPendingView, ScriptsRejectedView, ScriptsScoreAllView, ScriptsScoreCourseView, ScriptsTreatedView, ScriptsView, get_correctors, get_mass_students, QuotasView
 from django.conf.urls import url
 
 
 urlpatterns = [
     url(r'^scripts/periods/(?P<period_id>.*)/(?P<pk>.*)/detail/$', ScriptView.as_view(), name="teleforma-exam-script-detail"),
+    url(r'^scripts/periods/(?P<period_id>.*)/(?P<pk>.*)/download/$', ScriptDownloadView.as_view(), name="teleforma-exam-script-download"),
     url(r'^scripts/periods/(?P<period_id>.*)/list/$', ScriptsView.as_view(), name="teleforma-exam-script-list"),
     url(r'^scripts/periods/(?P<period_id>.*)/create/$', ScriptCreateView.as_view(), name="teleforma-exam-script-create"),
     url(r'^scripts/periods/(?P<period_id>.*)/pending/$', ScriptsPendingView.as_view(), name="teleforma-exam-scripts-pending"),
index 7a7107d1a6250a3d4e65e005f6fdf23318de4006..9708159a0e72469b0a0a4ace92735ace787ff71b 100755 (executable)
@@ -3,6 +3,7 @@
 # Create your views here.
 
 import datetime
+import io
 import json
 
 import numpy as np
@@ -20,6 +21,14 @@ from django.utils.translation import ugettext_lazy as _
 from django.views.generic.base import View
 from django.views.generic.edit import CreateView, UpdateView
 from django.views.generic.list import ListView
+from pdfannotator.models import Annotation, AnnotationComment
+from pypdf import PdfReader, PdfWriter
+from reportlab.lib.pagesizes import A4
+from reportlab.lib.styles import getSampleStyleSheet
+from reportlab.lib.units import inch
+from reportlab.pdfgen import canvas
+from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer
+
 from teleforma.decorators import access_required, staff_required
 from teleforma.models.crfpa import Student
 from teleforma.views.core import CourseAccessMixin, get_courses
@@ -165,6 +174,90 @@ class ScriptView(ScriptMixinView, CourseAccessMixin, UpdateView):
     def dispatch(self, *args, **kwargs):
         return super(ScriptView, self).dispatch(*args, **kwargs)
 
+class ScriptDownloadView(ScriptMixinView, CourseAccessMixin):
+    """
+    download the script pdf with its annotations
+    """
+
+    def get(self, request, *args, **kwargs):
+        script = get_object_or_404(Script, id=kwargs['pk'])
+
+        packet = io.BytesIO()
+        can = canvas.Canvas(packet, pagesize=A4)
+
+        # store text to be added in the last page
+        bottom_texts = []
+
+        page = 1
+        for annotation in Annotation.objects.filter(uuid=script.uuid).order_by('page'):
+            if annotation.page != page:
+                # we may have to skip some pages if there are no annotations on it
+                for i in range(annotation.page - page):
+                    # go to next page
+                    can.showPage()
+                page = annotation.page
+
+            # set text to red
+            can.setFillColorRGB(1,0,0)
+            
+            annotation_data = json.loads(annotation.content)
+            print(annotation_data)
+            text = "N/A"
+            # we handle only point (comment) and textbox (text) annotations
+            if annotation_data.get('type') == 'point':
+                print(annotation.uuid)
+                # print(uuid)
+                comment = AnnotationComment.objects.get(annotation_id=annotation.annotation_id, uuid=annotation.uuid)
+                text = comment.content
+                bottom_texts.append(text)
+                # add a number corresponding to the annotation
+                can.drawString(annotation_data['x'], -12 + A4[1] - annotation_data['y'], str(len(bottom_texts)))
+            elif annotation_data.get('type') == 'textbox':
+                can.drawString(annotation_data['x'], -12 + A4[1] - annotation_data['y'], annotation_data['content'])
+
+        can.save()
+
+        # create a new pdf with list of annotations
+        annotation_page = io.BytesIO()
+        doc = SimpleDocTemplate(annotation_page, pagesize=A4)
+        Story = []
+        styles = getSampleStyleSheet()
+        # for i in range(100):
+        for i, text in enumerate(bottom_texts):
+            comment = f"<strong>{i + 1}</strong> : {text}"
+            style = styles["Normal"]
+            p = Paragraph(comment, style)
+            Story.append(p)
+            Story.append(Spacer(1, 0.2*inch))
+        doc.build(Story)
+
+        #move to the beginning of the StringIO buffer
+        packet.seek(0)
+
+        # create a new PDF that contains reportlab content
+        new_pdf = PdfReader(packet)
+        # existing PDF
+        existing_pdf = PdfReader(script.file.path)
+        
+        # new pdf, merged from reportlab pdf and existing pdf
+        output = PdfWriter()
+        
+        # merge annotation layer pdf and existing pdf
+        for i, page in enumerate(existing_pdf.pages):
+            page.merge_page(new_pdf.pages[i])
+            output.add_page(page)
+
+        # finally, add annotations pages
+        new_pdf = PdfReader(annotation_page)
+        for page in new_pdf.pages:
+            output.add_page(page)
+        
+        response = HttpResponse(content_type='application/pdf')
+        response['Content-Disposition'] = 'attachment; filename="%s"' % script.file.name
+        output.write(response)
+        return response
+
+
 
 class ScriptsView(ScriptsListMixinView, ListView):
 
@@ -590,6 +683,7 @@ class MassScoreCreateView(ScoreCreateView):
     def dispatch(self, request, *args, **kwargs):
         return super(ScoreCreateView, self).dispatch(request, *args, **kwargs)
 
+       
 
 def get_mass_students(request):
     """
@@ -622,3 +716,6 @@ def get_mass_students(request):
 
     data = {'results': res, 'pagination': {'more': False}}
     return HttpResponse(json.dumps(data), content_type='application/json')
+
+
+