Jul 16 - Update facilities QR codes - report can upload up to 5 photos
This commit is contained in:
+37
-9
@@ -38,6 +38,30 @@ logger = logging.getLogger(__name__)
|
||||
bp = Blueprint('public', __name__, url_prefix='/f')
|
||||
|
||||
|
||||
#: Maximum number of photos an occupant may attach to a public report.
|
||||
MAX_REPORT_PHOTOS = 5
|
||||
|
||||
|
||||
def _save_report_photos(file_list):
|
||||
"""Save up to MAX_REPORT_PHOTOS uploaded photos from a public report.
|
||||
|
||||
Returns (photo_path, extra_paths) where photo_path is the primary evidence
|
||||
photo (or None) and extra_paths is a list of the remaining paths (or None).
|
||||
Splitting this way mirrors the Issue photo model: the first photo lives in
|
||||
`photo_path`, the rest in `mobile_photo_paths` so they all render together
|
||||
under "Photo Evidence" on the web (rule 44 — never `result_photos`).
|
||||
"""
|
||||
from app.routes.inspections import _save_photo
|
||||
saved = []
|
||||
for f in (file_list or [])[:MAX_REPORT_PHOTOS]:
|
||||
path = _save_photo(f, subfolder='issue_photos')
|
||||
if path:
|
||||
saved.append(path)
|
||||
photo_path = saved[0] if saved else None
|
||||
extra_paths = saved[1:] if len(saved) > 1 else None
|
||||
return photo_path, extra_paths
|
||||
|
||||
|
||||
def _facility_by_token_or_404(token: str) -> Facility:
|
||||
"""Resolve an ACTIVE facility from its public token, else 404."""
|
||||
if not token:
|
||||
@@ -320,9 +344,9 @@ def report_problem(token):
|
||||
return render_template('public/facility.html',
|
||||
form=form, token=token, **summary), 400
|
||||
|
||||
# Save optional photo through the shared, magic-byte-validated saver.
|
||||
from app.routes.inspections import _save_photo
|
||||
photo_path = _save_photo(form.photo.data, subfolder='issue_photos')
|
||||
# Save up to 5 optional photos through the shared, magic-byte-validated
|
||||
# saver. First → photo_path, the rest → mobile_photo_paths.
|
||||
photo_path, extra_photos = _save_report_photos(form.photos.data)
|
||||
|
||||
# Fold optional reporter identity + location into the description; the
|
||||
# public reporter is not a User, so reported_by stays NULL.
|
||||
@@ -342,6 +366,7 @@ def report_problem(token):
|
||||
severity = 'medium',
|
||||
description = description,
|
||||
photo_path = photo_path,
|
||||
mobile_photo_paths = extra_photos,
|
||||
status = 'open',
|
||||
reported_at = now_eastern(),
|
||||
reported_by = None,
|
||||
@@ -349,8 +374,9 @@ def report_problem(token):
|
||||
db.session.add(issue)
|
||||
db.session.commit()
|
||||
|
||||
logger.info('PUBLIC REPORT | issue_id=%s | facility_id=%s | ip=%s | photo=%s',
|
||||
issue.id, facility.id, request.remote_addr, bool(photo_path))
|
||||
_photo_count = (1 if photo_path else 0) + (len(extra_photos) if extra_photos else 0)
|
||||
logger.info('PUBLIC REPORT | issue_id=%s | facility_id=%s | ip=%s | photos=%s',
|
||||
issue.id, facility.id, request.remote_addr, _photo_count)
|
||||
|
||||
# Reuse the standard issue-created routing (staff + facility customers).
|
||||
notify_by_matrix(
|
||||
@@ -390,8 +416,8 @@ def area_report_problem(token):
|
||||
return render_template('public/area.html',
|
||||
form=form, token=token, **summary), 400
|
||||
|
||||
from app.routes.inspections import _save_photo
|
||||
photo_path = _save_photo(form.photo.data, subfolder='issue_photos')
|
||||
# Save up to 5 optional photos (first → photo_path, rest → mobile_photo_paths).
|
||||
photo_path, extra_photos = _save_report_photos(form.photos.data)
|
||||
|
||||
# The area is known from the QR token, so we set area_id directly and note
|
||||
# the source. A public reporter is not a User, so reported_by stays NULL.
|
||||
@@ -411,6 +437,7 @@ def area_report_problem(token):
|
||||
severity = 'medium',
|
||||
description = description,
|
||||
photo_path = photo_path,
|
||||
mobile_photo_paths = extra_photos,
|
||||
status = 'open',
|
||||
reported_at = now_eastern(),
|
||||
reported_by = None,
|
||||
@@ -418,8 +445,9 @@ def area_report_problem(token):
|
||||
db.session.add(issue)
|
||||
db.session.commit()
|
||||
|
||||
logger.info('PUBLIC REPORT | issue_id=%s | area_id=%s | facility_id=%s | ip=%s | photo=%s',
|
||||
issue.id, area.id, facility.id, request.remote_addr, bool(photo_path))
|
||||
_photo_count = (1 if photo_path else 0) + (len(extra_photos) if extra_photos else 0)
|
||||
logger.info('PUBLIC REPORT | issue_id=%s | area_id=%s | facility_id=%s | ip=%s | photos=%s',
|
||||
issue.id, area.id, facility.id, request.remote_addr, _photo_count)
|
||||
|
||||
notify_by_matrix(
|
||||
event_type = 'issue_created',
|
||||
|
||||
@@ -160,9 +160,13 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.photo.label(class="form-label small fw-semibold") }}
|
||||
{{ form.photo(class="form-control", accept="image/*") }}
|
||||
{% for e in form.photo.errors %}
|
||||
{{ form.photos.label(class="form-label small fw-semibold") }}
|
||||
{{ form.photos(class="form-control", accept="image/*", id="reportPhotos", multiple=true) }}
|
||||
<div class="form-text">You can attach up to 5 photos.</div>
|
||||
<div class="text-danger small mt-1" id="photoLimitMsg" style="display:none;">
|
||||
Please select no more than 5 photos — only the first 5 will be used.
|
||||
</div>
|
||||
{% for e in form.photos.errors %}
|
||||
<div class="text-danger small mt-1">{{ e }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -177,5 +181,21 @@
|
||||
Janitorial Quality Control
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
var input = document.getElementById('reportPhotos');
|
||||
var msg = document.getElementById('photoLimitMsg');
|
||||
if (!input) { return; }
|
||||
input.addEventListener('change', function () {
|
||||
if (input.files && input.files.length > 5) {
|
||||
if (msg) { msg.style.display = 'block'; }
|
||||
input.value = ''; // clear an over-limit selection so they re-pick
|
||||
} else if (msg) {
|
||||
msg.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -159,9 +159,13 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.photo.label(class="form-label small fw-semibold") }}
|
||||
{{ form.photo(class="form-control", accept="image/*") }}
|
||||
{% for e in form.photo.errors %}
|
||||
{{ form.photos.label(class="form-label small fw-semibold") }}
|
||||
{{ form.photos(class="form-control", accept="image/*", id="reportPhotos", multiple=true) }}
|
||||
<div class="form-text">You can attach up to 5 photos.</div>
|
||||
<div class="text-danger small mt-1" id="photoLimitMsg" style="display:none;">
|
||||
Please select no more than 5 photos — only the first 5 will be used.
|
||||
</div>
|
||||
{% for e in form.photos.errors %}
|
||||
<div class="text-danger small mt-1">{{ e }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -176,5 +180,21 @@
|
||||
Janitorial Quality Control
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
var input = document.getElementById('reportPhotos');
|
||||
var msg = document.getElementById('photoLimitMsg');
|
||||
if (!input) { return; }
|
||||
input.addEventListener('change', function () {
|
||||
if (input.files && input.files.length > 5) {
|
||||
if (msg) { msg.style.display = 'block'; }
|
||||
input.value = ''; // clear an over-limit selection so they re-pick
|
||||
} else if (msg) {
|
||||
msg.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+4
-4
@@ -315,10 +315,10 @@ class PublicIssueReportForm(FlaskForm):
|
||||
validators=[Optional(), Length(max=100)])
|
||||
reporter_contact = StringField('Email or phone (optional)',
|
||||
validators=[Optional(), Length(max=120)])
|
||||
photo = FileField('Add a photo (optional)',
|
||||
validators=[Optional(),
|
||||
FileAllowed(['jpg', 'jpeg', 'png', 'gif'],
|
||||
'Images only (jpg, png, gif).')])
|
||||
photos = MultipleFileField('Add photos (optional, up to 5)',
|
||||
validators=[Optional(),
|
||||
FileAllowed(['jpg', 'jpeg', 'png', 'gif'],
|
||||
'Images only (jpg, png, gif).')])
|
||||
website = StringField('Website') # honeypot — must stay empty
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user