diff --git a/intranet/apps/printing/forms.py b/intranet/apps/printing/forms.py index deddafa04d0..e7434753791 100644 --- a/intranet/apps/printing/forms.py +++ b/intranet/apps/printing/forms.py @@ -18,7 +18,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if printers: - self.fields["printer"].choices = [("", "Select a printer...")] + list(printers.items()) + self.fields["printer"].choices = [("", "Select a printer...")] + [(printer, desc) for printer, (desc, alerts) in printers.items()] def validate_size(self): filesize = self.file.__sizeof__() diff --git a/intranet/apps/printing/views.py b/intranet/apps/printing/views.py index 8f8626a1a7a..d581c799461 100644 --- a/intranet/apps/printing/views.py +++ b/intranet/apps/printing/views.py @@ -1,3 +1,4 @@ +import datetime import logging import math import os @@ -5,7 +6,7 @@ import subprocess import tempfile from io import BytesIO -from typing import Dict, Optional +from typing import Dict, Optional, Tuple import magic from django.conf import settings @@ -62,6 +63,29 @@ def set_user_ratelimit_status(username: str) -> None: cache.incr(cache_key) +def parse_alerts(alerts: str) -> Tuple[str, bool]: + known_alerts = { + "paused": "unavailable", + "media-empty-error": "out of paper", + "media-empty-warning": "out of paper", + "media-jam-error": "jammed", + "media-jam-warning": "jammed", + "none": "working", + } + alerts = alerts.split() + alerts_text = ", ".join(known_alerts.get(alert, "error") for alert in alerts) + error_alerts = ["paused"] + broken_alerts = ["media-empty-error", "media-empty-warning", "media-jam-error", "media-jam-warning"] + printer_class = "working" + for alert in alerts: + if alert in error_alerts or alert not in known_alerts: + printer_class = "error" + break + if alert in broken_alerts: + printer_class = "broken" + return alerts_text, printer_class + + def get_printers() -> Dict[str, str]: """Returns a dictionary mapping name:description for available printers. @@ -86,8 +110,9 @@ def get_printers() -> Dict[str, str]: except (subprocess.CalledProcessError, subprocess.TimeoutExpired): return [] - PRINTER_LINE_RE = re.compile(r"^printer\s+(\w+)\s+(?!disabled)", re.ASCII) + PRINTER_LINE_RE = re.compile(r"^printer\s+(\w+)", re.ASCII) DESCRIPTION_LINE_RE = re.compile(r"^\s+Description:\s+(.*)\s*$", re.ASCII) + ALERTS_LINE_RE = re.compile(r"^\s+Alerts:\s+(.*)\s*$", re.ASCII) printers = {} last_name = None @@ -98,19 +123,23 @@ def get_printers() -> Dict[str, str]: name = match.group(1) if name != "Please_Select_a_Printer": # By default, use the name of the printer instead of the description - printers[name] = name + printers[name] = [name] # Record the name of the printer so when we parse the rest of the # extended description we know which printer it's referring to. last_name = name - elif last_name is not None: - match = DESCRIPTION_LINE_RE.match(line) - if match is not None: - # Pull out the description - description = match.group(1) - # And make sure we don't set an empty description - if description: - printers[last_name] = description - last_name = None + elif last_name is not None: + description_match = DESCRIPTION_LINE_RE.match(line) + if description_match is not None: + # Pull out the description + description = description_match.group(1) + # And make sure we don't set an empty description + if description: + printers[last_name] = [description] + alerts_match = ALERTS_LINE_RE.match(line) + if alerts_match is not None: + alerts = alerts_match.group(1) + printers[last_name].append(alerts) + last_name = None cache.set(key, printers, timeout=settings.CACHE_AGE["printers_list"]) return printers @@ -300,8 +329,11 @@ def html_to_pdf(template_src, filename, context=None): def print_job(obj: PrintJob, do_print: bool = True): printer = obj.printer - if printer not in get_printers().keys(): + all_printers = get_printers() + if printer not in all_printers: raise Exception("Printer not authorized.") + if parse_alerts(all_printers[printer][1])[1] == "error": + raise Exception("Printer unavailable.") if not obj.file: raise InvalidInputPrintingError("No file given to print.") @@ -467,6 +499,9 @@ def print_view(request): messages.error(request, "You don't have printer access outside of the TJ network.") return redirect("index") + if request.method == "GET" and "refresh" in request.GET and request.user.is_printing_admin: + cache.delete("printing:printers") + printers = get_printers() if request.method == "POST": form = PrintJobForm(request.POST, request.FILES, printers=printers) @@ -494,5 +529,10 @@ def print_view(request): ) else: form = PrintJobForm(printers=printers) - context = {"form": form} + alerts = {} + for printer in printers: + alerts[printer] = parse_alerts(printers[printer][1]) + elapsed_seconds = settings.CACHE_AGE["printers_list"] - cache.ttl("printing:printers") + start_time = datetime.datetime.now() - datetime.timedelta(seconds=elapsed_seconds) + context = {"form": form, "alerts": alerts, "updated_time": start_time.strftime("%-I:%M:%S %p")} return render(request, "printing/print.html", context) diff --git a/intranet/settings/__init__.py b/intranet/settings/__init__.py index 492fc397c56..7d205fec2b9 100644 --- a/intranet/settings/__init__.py +++ b/intranet/settings/__init__.py @@ -340,6 +340,7 @@ "schedule.widget", "dashboard.widgets", "profile", + "printing", "polls", "groups", "board", diff --git a/intranet/static/css/printing.scss b/intranet/static/css/printing.scss new file mode 100644 index 00000000000..871ffaa3a3f --- /dev/null +++ b/intranet/static/css/printing.scss @@ -0,0 +1,38 @@ +.primary-content .selectize-control { + height: 42px; +} + +.primary-content tr th, +.primary-content tr td { + padding: 5px; +} + +.primary-content form { + float: left; + width: 60%; +} + +.print-status-container { + float: left; + padding: 10px; +} + +.printer-status { + text-align: center; + min-width: 200px; + margin: 15px; + padding: 15px; + border: 3px solid #888; +} + +.working { + background-color: #6BFF6B; +} + +.broken { + background-color: yellow; +} + +.error { + background-color: #FF9E9E; +} \ No newline at end of file diff --git a/intranet/templates/printing/print.html b/intranet/templates/printing/print.html index 435593e6386..8e29b3c3f8a 100644 --- a/intranet/templates/printing/print.html +++ b/intranet/templates/printing/print.html @@ -9,16 +9,7 @@ {% block css %} {{ block.super }} - + {% stylesheet 'printing' %} {% endblock %} {% block js %} @@ -45,5 +36,9 @@

Printing


{% include "printing/print_form.html" %} + + {% endblock %} diff --git a/intranet/templates/printing/print_status.html b/intranet/templates/printing/print_status.html new file mode 100644 index 00000000000..e19aa8cdd2a --- /dev/null +++ b/intranet/templates/printing/print_status.html @@ -0,0 +1,10 @@ +

Printer status:

+{% for printer, alert in alerts.items %} +
+ {{ printer }}: {{ alert.0 }} +
+{% endfor %} +Updated at: {{ updated_time }} +{% if user.is_printing_admin %} +(Update) +{% endif %} \ No newline at end of file