Add configurable max revisions limit with customer counter and operator override
This commit is contained in:
parent
5b6012aa3d
commit
506d07179a
5 changed files with 99 additions and 22 deletions
72
app.py
72
app.py
|
|
@ -33,6 +33,8 @@ import ssl
|
|||
from email.message import EmailMessage
|
||||
from pathlib import Path
|
||||
|
||||
import json
|
||||
|
||||
# Flask and related imports
|
||||
from flask import Flask, request, render_template, redirect, url_for, flash, session, send_from_directory, abort, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
|
|
@ -202,7 +204,19 @@ def play(token):
|
|||
req = get_request_by_token(token)
|
||||
if not req:
|
||||
abort(404)
|
||||
return render_template('player.html', req=req)
|
||||
|
||||
# Load runtime max revisions setting.
|
||||
cfg_path = Path(current_app.config['UPLOAD_FOLDER']).parent / 'booth_settings.json'
|
||||
runtime_settings = {}
|
||||
if cfg_path.exists():
|
||||
try:
|
||||
runtime_settings = json.loads(cfg_path.read_text())
|
||||
except (json.JSONDecodeError, OSError):
|
||||
runtime_settings = {}
|
||||
max_revisions = runtime_settings.get('max_revisions', current_app.config['MAX_REVISIONS'])
|
||||
revisions_left = max(0, max_revisions - (req.get('revision_count') or 0))
|
||||
|
||||
return render_template('player.html', req=req, revisions_left=revisions_left)
|
||||
|
||||
|
||||
@app.route('/play/<token>/approve', methods=['POST'])
|
||||
|
|
@ -236,10 +250,26 @@ def revise(token):
|
|||
so the operator sees it in the dashboard queue.
|
||||
"""
|
||||
note = request.form.get('revision_note', '').strip()
|
||||
# Increment revision counter and archive current files before new versions are uploaded.
|
||||
req = get_request_by_token(token)
|
||||
if req:
|
||||
new_count = (req.get('revision_count') or 0) + 1
|
||||
if not req:
|
||||
abort(404)
|
||||
|
||||
# Enforce max revisions limit for customer-submitted revisions.
|
||||
cfg_path = Path(current_app.config['UPLOAD_FOLDER']).parent / 'booth_settings.json'
|
||||
runtime_settings = {}
|
||||
if cfg_path.exists():
|
||||
try:
|
||||
runtime_settings = json.loads(cfg_path.read_text())
|
||||
except (json.JSONDecodeError, OSError):
|
||||
runtime_settings = {}
|
||||
max_revisions = runtime_settings.get('max_revisions', current_app.config['MAX_REVISIONS'])
|
||||
current_count = req.get('revision_count') or 0
|
||||
if current_count >= max_revisions:
|
||||
flash('Revision limit reached. Please speak to the booth operator if you need further changes.', 'error')
|
||||
return redirect(url_for('play', token=token))
|
||||
|
||||
# Increment revision counter and archive current files before new versions are uploaded.
|
||||
new_count = current_count + 1
|
||||
upload_dir = Path(current_app.config['UPLOAD_FOLDER']) / str(req['id'])
|
||||
if upload_dir.exists():
|
||||
for field, version in (('song_a_path', 'A'), ('song_b_path', 'B')):
|
||||
|
|
@ -249,15 +279,12 @@ def revise(token):
|
|||
archived = upload_dir / f"Rev{new_count}-{old.name}"
|
||||
try:
|
||||
old.rename(archived)
|
||||
# Keep path pointing to the archived file; operator will upload new files.
|
||||
req[field] = str(archived)
|
||||
except OSError:
|
||||
pass
|
||||
update_request(req['id'], revision_note=note, status='revisions_requested',
|
||||
song_a_path=req.get('song_a_path'), song_b_path=req.get('song_b_path'),
|
||||
customer_approved='none', revision_count=new_count)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
# NOTE: No operator email is sent; the dashboard is the single queue.
|
||||
flash('Your feedback has been saved. We will regenerate and update you.', 'success')
|
||||
|
|
@ -479,6 +506,16 @@ def admin_settings():
|
|||
for req in all_requests:
|
||||
status_counts[req['status']] = status_counts.get(req['status'], 0) + 1
|
||||
|
||||
# Load persistent runtime settings (max_revisions overrides env var if set).
|
||||
cfg_path = Path(current_app.config['UPLOAD_FOLDER']).parent / 'booth_settings.json'
|
||||
runtime_settings = {}
|
||||
if cfg_path.exists():
|
||||
try:
|
||||
runtime_settings = json.loads(cfg_path.read_text())
|
||||
except (json.JSONDecodeError, OSError):
|
||||
runtime_settings = {}
|
||||
current_max_revisions = runtime_settings.get('max_revisions', current_app.config['MAX_REVISIONS'])
|
||||
|
||||
# Compute upload folder stats.
|
||||
total_upload_size = 0
|
||||
upload_file_count = 0
|
||||
|
|
@ -555,6 +592,24 @@ def admin_settings():
|
|||
flash('System reset complete. All orders and files have been cleared.', 'success')
|
||||
return redirect(url_for('admin_settings'))
|
||||
|
||||
elif action == 'save_max_revisions':
|
||||
# Update the MAX_REVISIONS config from the settings form.
|
||||
try:
|
||||
val = int(request.form.get('max_revisions', '2').strip())
|
||||
if val < 0:
|
||||
raise ValueError
|
||||
# Store in a simple config file so it persists across restarts.
|
||||
cfg_path = Path(current_app.config['UPLOAD_FOLDER']).parent / 'booth_settings.json'
|
||||
data = {}
|
||||
if cfg_path.exists():
|
||||
data = json.loads(cfg_path.read_text())
|
||||
data['max_revisions'] = val
|
||||
cfg_path.write_text(json.dumps(data))
|
||||
flash(f'Maximum revisions set to {val}.', 'success')
|
||||
except ValueError:
|
||||
flash('Invalid revision limit. Please enter a non-negative number.', 'error')
|
||||
return redirect(url_for('admin_settings'))
|
||||
|
||||
return render_template(
|
||||
'admin/settings.html',
|
||||
health=health,
|
||||
|
|
@ -566,7 +621,8 @@ def admin_settings():
|
|||
upload_dir_count=request_dir_count,
|
||||
upload_size=format_bytes(total_upload_size),
|
||||
db_path=str(db_path),
|
||||
upload_path=str(upload_root)
|
||||
upload_path=str(upload_root),
|
||||
current_max_revisions=current_max_revisions,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
1
booth_settings.json
Normal file
1
booth_settings.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"max_revisions": 1}
|
||||
|
|
@ -47,6 +47,9 @@ class Config:
|
|||
# Optional operator alert email. Currently unused because the dashboard is the queue.
|
||||
ADMIN_ALERT_EMAIL = os.environ.get('ADMIN_ALERT_EMAIL', '')
|
||||
|
||||
# Maximum number of revision rounds a customer is allowed to request automatically.
|
||||
MAX_REVISIONS = int(os.environ.get('MAX_REVISIONS', '2'))
|
||||
|
||||
# Public HTTPS URL used in customer emails and QR codes.
|
||||
PUBLIC_BASE_URL = os.environ.get('PUBLIC_BASE_URL', 'http://127.0.0.1:5000')
|
||||
|
||||
|
|
|
|||
|
|
@ -194,6 +194,18 @@
|
|||
<p><strong>Uploads:</strong><br><span class="path">{{ upload_path }}</span></p>
|
||||
</div>
|
||||
|
||||
<!-- Settings controls -->
|
||||
<div class="section">
|
||||
<h2>Booth Settings</h2>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="save_max_revisions">
|
||||
<label for="max_revisions">Maximum customer revisions allowed</label>
|
||||
<input type="number" id="max_revisions" name="max_revisions" min="0" value="{{ current_max_revisions }}">
|
||||
<p class="copy-hint">Set to 0 to disable customer-submitted revisions. Operators can still upload new versions manually.</p>
|
||||
<button type="submit">Save Revision Limit</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- System reset -->
|
||||
<div class="danger-zone">
|
||||
<h2>⚠️ Reset System</h2>
|
||||
|
|
|
|||
|
|
@ -130,14 +130,19 @@
|
|||
</div>
|
||||
</form>
|
||||
|
||||
{% if revisions_left > 0 %}
|
||||
<!-- Revision form: customer asks for changes -->
|
||||
<form method="POST" action="{{ url_for('revise', token=req.player_token) }}">
|
||||
<p style="color:#93c5fd;font-weight:600">Revisions remaining: {{ revisions_left }}</p>
|
||||
<label for="revision_note">Or ask for changes:</label>
|
||||
<textarea id="revision_note" name="revision_note" placeholder="e.g. make the chorus louder, swap a lyric..."></textarea>
|
||||
<div class="actions">
|
||||
<button type="submit" class="revision">Request Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<p style="margin-top:1rem;color:#f87171;font-weight:600">No revisions remaining. Please speak to the booth operator if you need further changes.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
|
|
|||
Reference in a new issue