-
Notifications
You must be signed in to change notification settings - Fork 103
print form website #899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
print form website #899
Changes from all commits
ae9093a
0659a27
fc3031a
e08e4d7
6dae31f
9567214
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,3 +20,7 @@ db = ocfmail | |
| user = ocfstats | ||
| password = password | ||
| db = ocfstats | ||
|
|
||
| [printing] | ||
| # Joe's VM | ||
| otp_url = http://joe:15011 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why did the devenv get updated as a side effect to this pr |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| uv.enable = true; | ||
| }; | ||
| packages = with pkgs; [ | ||
| cups | ||
| gnumake | ||
| libffi | ||
| pkg-config | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| import tempfile | ||
|
|
||
| import cups | ||
| import requests | ||
| from django import forms | ||
| from django.conf import settings | ||
| 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 | ||
| from ocfweb.printing import get_printers | ||
|
|
||
|
|
||
| class WebPrintForm(Form): | ||
| printer = forms.ChoiceField( | ||
| label='Select Printer', | ||
| ) | ||
| file = forms.FileField( | ||
| label='File to Print', | ||
| ) | ||
| otp = forms.CharField( | ||
| label='Verification Code (From front of room)', | ||
| min_length=6, | ||
| max_length=6, | ||
| widget=forms.TextInput(attrs={'placeholder': '123456'}), | ||
| ) | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
| printers = get_printers() | ||
|
|
||
| choices = [] | ||
| for name, info in printers.items(): | ||
| if info.get('printer-is-shared', True): | ||
| choices.append((name, info.get('printer-info', name))) | ||
|
|
||
| if not choices: | ||
| choices = [('', 'No printers available')] | ||
|
|
||
| self.fields['printer'].choices = choices | ||
|
|
||
| def clean_file(self): | ||
| return self.cleaned_data.get('file') | ||
|
|
||
| def clean_otp(self): | ||
| otp = self.cleaned_data.get('otp') | ||
| if not otp: | ||
| return otp | ||
|
|
||
| verify_url = f"{settings.PRINTING_OTP_URL}/verify/{otp}" | ||
| try: | ||
| response = requests.get(verify_url, timeout=5) | ||
| response.raise_for_status() | ||
| data = response.json() | ||
| if not data.get('valid'): | ||
| raise forms.ValidationError('Invalid code. Please get the code from the front of the room to enter.') | ||
| except (requests.RequestException, ValueError): | ||
| # Fail closed on server error | ||
| raise forms.ValidationError('Could not verify code with server. Please try again later.') | ||
|
|
||
| return otp | ||
|
|
||
|
|
||
| @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 printhost CUPS | ||
| cups.setUser(user) | ||
| conn = cups.Connection(host='printhost') | ||
| conn.printFile( | ||
| printer, | ||
| tmp.name, | ||
| uploaded_file.name, | ||
| {}, | ||
| ) | ||
|
|
||
| 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, | ||
| }, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| {% extends 'base.html' %} | ||
| {% load bootstrap %} | ||
|
|
||
| {% block content %} | ||
| <div class="row"> | ||
| <div class="col-sm-8 ocf-content-block"> | ||
| <p> | ||
| Welcome to web printing! You can upload a file here to print it to one of the lab printers. | ||
| </p> | ||
|
|
||
| <form method="post" enctype="multipart/form-data"> | ||
| {% csrf_token %} | ||
| <div style="max-width: 400px;"> | ||
| {{ form|bootstrap }} | ||
| </div> | ||
| <button type="submit" class="btn btn-primary"> | ||
| <span class="glyphicon glyphicon-print" aria-hidden="true"></span> | ||
| </button> | ||
| </form> | ||
| </div> | ||
|
|
||
| <div class="col-sm-4"> | ||
| <div class="panel panel-default"> | ||
| <div class="panel-heading"> | ||
| <h3 class="panel-title">Remaining Quota</h3> | ||
| </div> | ||
| <div class="panel-body"> | ||
| <dl> | ||
| <dt>Daily</dt> | ||
| <dd>{{ quota.daily }} pages</dd> | ||
| <dt>Semesterly</dt> | ||
| <dd>{{ quota.semesterly }} pages</dd> | ||
| </dl> | ||
| <p class="text-muted small"> | ||
| Quota is deducted automatically when you print. | ||
| For more info, see the <a href="{% url 'doc' 'services/lab/printing' %}">printing docs</a>. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| {% endblock %} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import cups | ||
|
|
||
| from ocfweb.caching import cache | ||
|
|
||
| @cache(ttl=60) | ||
| def get_printers(): | ||
| try: | ||
| conn = cups.Connection(host='printhost') | ||
| return conn.getPrinters() | ||
| except Exception: | ||
| return {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,15 +12,15 @@ | |
| from django.shortcuts import render | ||
| from matplotlib.figure import Figure | ||
| from ocflib.lab import stats | ||
| from ocflib.printing.printers import PRINTERS | ||
| from ocflib.printing.quota import get_connection | ||
| from ocflib.printing.quota import SEMESTERLY_QUOTA | ||
|
|
||
| from ocfweb.caching import periodic | ||
| from ocfweb.component.graph import plot_to_image_bytes | ||
| from ocfweb.printing import get_printers | ||
|
|
||
| ALL_PRINTERS = ('papercut', 'pagefault', 'logjam', 'logjam-old', 'deforestation') | ||
| ACTIVE_PRINTERS = ('papercut', 'pagefault', 'logjam') | ||
| ALL_PRINTERS = ('papercut', 'pagefault', 'logjam', 'logjam-old', 'deforestation', 'OCF-BW', 'OCF-Color') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please don't use the aggregate names here, these variables are referenced elsewhere in ocfweb
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see my stats branch for the proper way to do this, although it's far from finished https://github.com/ocf/ocfweb/tree/printer-stats-update |
||
| ACTIVE_PRINTERS = ('OCF-BW', 'OCF-Color') | ||
|
|
||
|
|
||
| def stats_printing(request: HttpRequest) -> HttpResponse: | ||
|
|
@@ -29,7 +29,7 @@ def stats_printing(request: HttpRequest) -> HttpResponse: | |
| 'stats/printing.html', | ||
| { | ||
| 'title': 'Printing Statistics', | ||
| 'current_printers': PRINTERS, | ||
| 'current_printers': get_printers().keys(), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why did you update the variables above and then not use them |
||
| 'toner_changes': _toner_changes(), | ||
| 'last_month': [ | ||
| date.today() - timedelta(days=i) | ||
|
|
@@ -75,7 +75,7 @@ def _toner_changes() -> List[Any]: | |
| printer, | ||
| _toner_used_by_printer(printer), | ||
| ) | ||
| for printer in ACTIVE_PRINTERS | ||
| for printer in get_printers().keys() | ||
| ] | ||
|
|
||
|
|
||
|
|
@@ -197,13 +197,14 @@ def _pages_printed_for_printer(printer: str, resolution: int = 100) -> List[Any] | |
|
|
||
| @periodic(3600) | ||
| def _pages_printed_data() -> List[Any]: | ||
| printers = set(ALL_PRINTERS) | set(get_printers().keys()) | ||
| return [ | ||
| { | ||
| 'name': printer, | ||
| 'animation': False, | ||
| 'data': _pages_printed_for_printer(printer), | ||
| } | ||
| for printer in ALL_PRINTERS | ||
| for printer in sorted(printers) | ||
| ] | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using joe's staff vm is not acceptable for deployment, also there's probably better ways to do this