From ae9093acaadaff90c938f64ebc0aeecb9f239226 Mon Sep 17 00:00:00 2001 From: Daniel Liu Date: Wed, 13 May 2026 11:02:51 -0700 Subject: [PATCH 1/6] feat: add printing page --- Dockerfile | 1 + devenv.nix | 1 + ocfweb/account/print.py | 81 +++++++++++++++++++++ ocfweb/account/templates/account/print.html | 43 +++++++++++ ocfweb/account/urls.py | 2 + ocfweb/templates/base.html | 3 + requirements-minimal.txt | 1 + requirements.txt | 2 + 8 files changed, 134 insertions(+) create mode 100644 ocfweb/account/print.py create mode 100644 ocfweb/account/templates/account/print.html diff --git a/Dockerfile b/Dockerfile index 40b66527f..df675cde9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN apt-get update \ cracklib-runtime \ libcrack2 \ libcrack2-dev \ + libcups2-dev \ libffi-dev \ libfreetype6-dev \ libpng-dev \ diff --git a/devenv.nix b/devenv.nix index 70762f7b2..78a9276ff 100644 --- a/devenv.nix +++ b/devenv.nix @@ -12,6 +12,7 @@ uv.enable = true; }; packages = with pkgs; [ + cups gnumake libffi pkg-config diff --git a/ocfweb/account/print.py b/ocfweb/account/print.py new file mode 100644 index 000000000..e23601a41 --- /dev/null +++ b/ocfweb/account/print.py @@ -0,0 +1,81 @@ +import tempfile +import cups +from django import forms +from django.contrib import messages +from django.http import HttpRequest +from django.http import HttpResponse +from django.shortcuts import redirect +from django.shortcuts import render +from ocflib.printing.quota import get_connection as get_quota_connection +from ocflib.printing.quota import get_quota + +from ocfweb.auth import login_required +from ocfweb.component.forms import Form +from ocfweb.component.session import logged_in_user + +# Printers available for web printing +ACTIVE_PRINTERS = ( + ('papercut', 'papercut (Double-sided)'), + ('pagefault', 'pagefault (Single-sided)'), + ('logjam', 'logjam (Double-sided)'), +) + + +class WebPrintForm(Form): + printer = forms.ChoiceField( + choices=ACTIVE_PRINTERS, + label='Select Printer', + ) + file = forms.FileField( + label='File to Print', + ) + + def clean_file(self): + return self.cleaned_data.get('file') + + +@login_required +def web_print(request: HttpRequest) -> HttpResponse: + user = logged_in_user(request) + + with get_quota_connection() as c: + quota = get_quota(c, user) + + if request.method == 'POST': + form = WebPrintForm(request.POST, request.FILES) + if form.is_valid(): + printer = form.cleaned_data['printer'] + uploaded_file = form.cleaned_data['file'] + + try: + with tempfile.NamedTemporaryFile() as tmp: + for chunk in uploaded_file.chunks(): + tmp.write(chunk) + tmp.flush() + + # Submit to CUPS + cups.setUser(user) + conn = cups.Connection(host='printhost.ocf.berkeley.edu') + conn.printFile( + printer, + tmp.name, + uploaded_file.name, + {} # Options can be added here + ) + + messages.success(request, f'Successfully submitted "{uploaded_file.name}" to {printer}.') + return redirect('web_print') + except Exception as e: + messages.error(request, f'Failed to print: {e}') + else: + form = WebPrintForm() + + return render( + request, + 'account/print.html', + { + 'title': 'Web Printing', + 'form': form, + 'quota': quota, + }, + ) diff --git a/ocfweb/account/templates/account/print.html b/ocfweb/account/templates/account/print.html new file mode 100644 index 000000000..b336a3084 --- /dev/null +++ b/ocfweb/account/templates/account/print.html @@ -0,0 +1,43 @@ +{% extends 'base.html' %} +{% load bootstrap %} + +{% block content %} +
+
+

+ Welcome to web printing! You can upload a file here to print it to one of the lab printers. +

+ +
+ {% csrf_token %} +
+ {{ form|bootstrap }} +
+ +
+
+ +
+
+
+

Remaining Quota

+
+
+
+
Daily
+
{{ quota.daily }} pages
+
Semesterly
+
{{ quota.semesterly }} pages
+
+

+ Quota is deducted automatically when you print. + For more info, see the printing docs. +

+
+
+
+
+{% endblock %} diff --git a/ocfweb/account/urls.py b/ocfweb/account/urls.py index c6c363cd2..f39a349a8 100644 --- a/ocfweb/account/urls.py +++ b/ocfweb/account/urls.py @@ -2,6 +2,7 @@ from ocfweb.account.chpass import change_password from ocfweb.account.commands import commands +from ocfweb.account.print import web_print from ocfweb.account.register import account_created from ocfweb.account.register import account_pending from ocfweb.account.register import recommend @@ -19,6 +20,7 @@ urlpatterns = [ re_path(r'^password/$', change_password, name='change_password'), re_path(r'^commands/$', commands, name='commands'), + re_path(r'^print/$', web_print, name='web_print'), # account creation re_path(r'^register/$', request_account, name='register'), diff --git a/ocfweb/templates/base.html b/ocfweb/templates/base.html index a25de9ba7..d8664d091 100644 --- a/ocfweb/templates/base.html +++ b/ocfweb/templates/base.html @@ -59,6 +59,8 @@