diff --git a/Ion.egg-info/PKG-INFO b/Ion.egg-info/PKG-INFO index 6524fd61927..c18a45dad4d 100644 --- a/Ion.egg-info/PKG-INFO +++ b/Ion.egg-info/PKG-INFO @@ -11,6 +11,71 @@ Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python :: 3.8 Classifier: Framework :: Django :: 3.2 License-File: COPYING +Requires-Dist: argon2-cffi==21.3.0 +Requires-Dist: autobahn==22.7.1 +Requires-Dist: autopep8==1.7.0 +Requires-Dist: Babel==2.10.3 +Requires-Dist: bcrypt==4.0.0 +Requires-Dist: beautifulsoup4==4.11.1 +Requires-Dist: black==23.1.0 +Requires-Dist: bleach==5.0.1 +Requires-Dist: celery==5.2.7 +Requires-Dist: certifi==2022.12.7 +Requires-Dist: channels==3.0.5 +Requires-Dist: channels-redis==3.4.1 +Requires-Dist: contextlib2==21.6.0 +Requires-Dist: cryptography==41.0.0 +Requires-Dist: decorator==5.1.1 +Requires-Dist: Django==3.2.20 +Requires-Dist: django-cacheops==7.0.1 +Requires-Dist: django-cors-headers==3.13.0 +Requires-Dist: django-debug-toolbar==3.6.0 +Requires-Dist: django-extensions==3.2.0 +Requires-Dist: django-formtools==2.3 +Requires-Dist: django-inline-svg==0.1.1 +Requires-Dist: django-maintenance-mode==0.16.3 +Requires-Dist: django-oauth-toolkit==2.1.0 +Requires-Dist: django-pipeline==2.0.9 +Requires-Dist: django-prometheus==2.2.0 +Requires-Dist: django-redis-cache==3.0.1 +Requires-Dist: django-redis-sessions==0.6.2 +Requires-Dist: django-referrer-policy==1.0 +Requires-Dist: django-requestlogging-redux==1.2.1 +Requires-Dist: django-simple-history==3.1.1 +Requires-Dist: django-user-agents==0.4.0 +Requires-Dist: django-widget-tweaks==1.4.12 +Requires-Dist: djangorestframework==3.14.0 +Requires-Dist: docutils==0.19 +Requires-Dist: Fabric3==1.14.post1 +Requires-Dist: flower==1.2.0 +Requires-Dist: gunicorn==20.1.0 +Requires-Dist: hiredis==2.0.0 +Requires-Dist: ipython==8.10.0 +Requires-Dist: isort==5.12.0 +Requires-Dist: lxml==4.9.1 +Requires-Dist: objgraph==3.5.0 +Requires-Dist: pexpect==4.8.0 +Requires-Dist: prometheus-client==0.17.0 +Requires-Dist: psycopg2==2.9.3 +Requires-Dist: pycryptodome==3.18.0 +Requires-Dist: pyrankvote==2.0.5 +Requires-Dist: pysftp==0.2.9 +Requires-Dist: python-dateutil==2.8.2 +Requires-Dist: python-magic==0.4.27 +Requires-Dist: reportlab==3.6.11 +Requires-Dist: requests==2.31.0 +Requires-Dist: requests-oauthlib==1.3.1 +Requires-Dist: sentry-sdk==1.15.0 +Requires-Dist: setuptools-git==1.2 +Requires-Dist: Sphinx==5.2.3 +Requires-Dist: sphinx-bootstrap-theme==0.8.1 +Requires-Dist: tblib==1.7.0 +Requires-Dist: vine==5.0.0 +Requires-Dist: xhtml2pdf==0.2.11 +Requires-Dist: asgiref>=3.3.4 +Requires-Dist: pillow>=9.0.0 +Requires-Dist: tinycss2 +Requires-Dist: twisted>=21.7.0 ********** Intranet 3 diff --git a/Ion.egg-info/SOURCES.txt b/Ion.egg-info/SOURCES.txt index 17b9a834f69..abd79e19cee 100644 --- a/Ion.egg-info/SOURCES.txt +++ b/Ion.egg-info/SOURCES.txt @@ -824,6 +824,7 @@ intranet/apps/printing/migrations/0006_printjob_page_range.py intranet/apps/printing/migrations/0007_printjob_duplex.py intranet/apps/printing/migrations/0008_auto_20160828_2058.py intranet/apps/printing/migrations/0009_printjob_fit.py +intranet/apps/printing/migrations/0010_alter_printjob_duplex.py intranet/apps/printing/migrations/__init__.py intranet/apps/schedule/__init__.py intranet/apps/schedule/admin.py @@ -3618,6 +3619,7 @@ intranet/templates/preferences/preferences.html intranet/templates/preferences/privacy_options.html intranet/templates/printing/print.html intranet/templates/printing/print_form.html +intranet/templates/printing/title_page.html intranet/templates/rest_framework/api.html intranet/templates/rest_framework/login.html intranet/templates/schedule/admin_add.html diff --git a/Ion.egg-info/requires.txt b/Ion.egg-info/requires.txt index 3fe711ce8bf..99c2208d9f5 100644 --- a/Ion.egg-info/requires.txt +++ b/Ion.egg-info/requires.txt @@ -58,6 +58,7 @@ Sphinx==5.2.3 sphinx-bootstrap-theme==0.8.1 tblib==1.7.0 vine==5.0.0 +xhtml2pdf==0.2.11 asgiref>=3.3.4 pillow>=9.0.0 tinycss2 diff --git a/docs/rtd-requirements.txt b/docs/rtd-requirements.txt index 5cc337f6a12..47927e92556 100644 --- a/docs/rtd-requirements.txt +++ b/docs/rtd-requirements.txt @@ -58,6 +58,7 @@ Sphinx==5.2.3 sphinx-bootstrap-theme==0.8.1 tblib==1.7.0 vine==5.0.0 +xhtml2pdf==0.2.11 # Not direct dependencies, but need to be bumped for some reason # (for example, bug or security fixes) diff --git a/intranet/apps/printing/migrations/0010_alter_printjob_duplex.py b/intranet/apps/printing/migrations/0010_alter_printjob_duplex.py new file mode 100644 index 00000000000..9dbadbc63ac --- /dev/null +++ b/intranet/apps/printing/migrations/0010_alter_printjob_duplex.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.20 on 2023-11-02 01:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('printing', '0009_printjob_fit'), + ] + + operations = [ + migrations.AlterField( + model_name='printjob', + name='duplex', + field=models.BooleanField(default=True, verbose_name='Double-sided'), + ), + ] diff --git a/intranet/apps/printing/models.py b/intranet/apps/printing/models.py index d36ec040db5..641f38163d6 100644 --- a/intranet/apps/printing/models.py +++ b/intranet/apps/printing/models.py @@ -26,7 +26,7 @@ class PrintJob(models.Model): time = models.DateTimeField(auto_now_add=True) printed = models.BooleanField(default=False) num_pages = models.IntegerField(default=0) - duplex = models.BooleanField(default=True) + duplex = models.BooleanField(default=True, verbose_name="Double-sided") fit = models.BooleanField(default=False, verbose_name="Fit-to-page") def __str__(self): diff --git a/intranet/apps/printing/views.py b/intranet/apps/printing/views.py index 08a561436f3..0ca6783f15f 100644 --- a/intranet/apps/printing/views.py +++ b/intranet/apps/printing/views.py @@ -4,16 +4,20 @@ import re import subprocess import tempfile +from io import BytesIO from typing import Dict, Optional import magic from sentry_sdk import add_breadcrumb, capture_exception +from xhtml2pdf import pisa from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required from django.core.cache import cache from django.shortcuts import redirect, render +from django.template.loader import get_template +from django.utils import timezone from django.utils.text import slugify from ..auth.decorators import deny_restricted @@ -248,6 +252,23 @@ def check_page_range(page_range: str, max_pages: int) -> Optional[int]: return pages +def html_to_pdf(template_src, filename, context=None): + if context is None: + context = {} + template = get_template(template_src) + html = template.render(context) + result = BytesIO() + pdf = pisa.pisaDocument(BytesIO(html.encode("ISO-8859-1")), result) + if not pdf.err: + filename_without_extension = os.path.basename(os.path.splitext(filename)[0]) + filename = filename_without_extension + ".pdf" + title_tmpfile_fd, title_tmpfile_name = tempfile.mkstemp(prefix=f"ion_title_print_{filename}") + with open(title_tmpfile_fd, "wb") as f: + f.write(result.getvalue()) + return title_tmpfile_name + return None + + def print_job(obj: PrintJob, do_print: bool = True): printer = obj.printer if printer not in get_printers().keys(): @@ -352,14 +373,28 @@ def print_job(obj: PrintJob, do_print: bool = True): if obj.fit: args.extend(["-o", "fit-to-page"]) + title_page = html_to_pdf( + "printing/title_page.html", + final_filename, + { + "obj": obj, + "filename": filebase, + "time": timezone.now(), + "pages": num_pages, + }, + ) + + delete_filenames.add(title_page) + try: + subprocess.check_output(["lpr", "-P", printer, title_page], stderr=subprocess.STDOUT, universal_newlines=True) subprocess.check_output(args, stderr=subprocess.STDOUT, universal_newlines=True) except subprocess.CalledProcessError as e: if "is not accepting jobs" in e.output: raise Exception(e.output.strip()) from e logger.error("Could not run lpr (returned %d): %s", e.returncode, e.output.strip()) - raise Exception("An error occured while printing your file: {}".format(e.output.strip())) from e + raise Exception("An error occurred while printing your file: {}".format(e.output.strip())) from e obj.printed = True obj.save() @@ -394,9 +429,11 @@ def print_view(request): messages.success( request, "Your file was submitted to the printer. " - "If the printers are experiencing trouble, please contact the " - "Student Systems Administrators by filling out the feedback " - "form.", + "Do not re-print this job if it does not come out of the printer - " + "in nearly all cases, the job has been received and re-printing" + "will cause multiple copies to be printed." + "Ask for help instead by contacting the " + "Student Systems Administrators by filling out the feedback form.", ) else: form = PrintJobForm(printers=printers) diff --git a/intranet/settings/__init__.py b/intranet/settings/__init__.py index f1a64bb5287..70d9025ba16 100644 --- a/intranet/settings/__init__.py +++ b/intranet/settings/__init__.py @@ -210,7 +210,8 @@ # The maximum number of pages in one document that can be # printed through the printing functionality (determined through pdfinfo) -PRINTING_PAGES_LIMIT = 15 +# even number preferred to allow for maximum utilization of duplex printing +PRINTING_PAGES_LIMIT = 16 # The maximum file upload and download size for files FILES_MAX_UPLOAD_SIZE = 200 * 1024 * 1024 diff --git a/intranet/templates/printing/print_form.html b/intranet/templates/printing/print_form.html index d595ee9cd93..fa479a42b12 100644 --- a/intranet/templates/printing/print_form.html +++ b/intranet/templates/printing/print_form.html @@ -22,9 +22,10 @@
The following restrictions apply:
diff --git a/intranet/templates/printing/title_page.html b/intranet/templates/printing/title_page.html new file mode 100644 index 00000000000..d46b33e78b1 --- /dev/null +++ b/intranet/templates/printing/title_page.html @@ -0,0 +1,50 @@ + + + + + + +

+ +
+

Name: {{ obj.user.full_name }}

+

Username: {{ obj.user.username }}

+ {% if obj.user.student_id %} +

Student ID: {{ obj.user.student_id }}

+ {% endif %} +

Time: {{ time }}

+

Filename: {{ filename }}

+

Pages: {{ pages }}

+ +
+ +
+

Printer: {{ obj.printer }}

+

Duplex: {{ obj.duplex }}

+

Fit-to-page: {{ obj.fit }}

+ {% if obj.page_range %} +

Page range: {{ obj.page_range }}

+ {% endif %} +
+ Print Job ID: {{ obj.id }} +
+ + + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f29d37eb1d0..8d4ebfd0be8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -58,6 +58,7 @@ Sphinx==5.2.3 sphinx-bootstrap-theme==0.8.1 tblib==1.7.0 vine==5.0.0 +xhtml2pdf==0.2.11 # Not direct dependencies, but need to be bumped for some reason # (for example, bug or security fixes)