Jul 14 - Using CDN - Phase 4a
This commit is contained in:
@@ -95,6 +95,13 @@ def _parse_datetime(value):
|
||||
return None
|
||||
|
||||
|
||||
def _media(key):
|
||||
"""Absolute display URL for a storage key (presigned on R2, absolute-static
|
||||
on local). '' for falsy keys. Used for iPad image rendering."""
|
||||
from app.utils import storage
|
||||
return storage.media_url(key, external=True) if key else ''
|
||||
|
||||
|
||||
def _inspection_payload(inspection):
|
||||
"""Serialize an Inspection to the dict returned in API responses."""
|
||||
# Extract form responses from the notes JSON blob.
|
||||
@@ -136,6 +143,14 @@ def _inspection_payload(inspection):
|
||||
if inspection.completed_at else None,
|
||||
'mobile_local_id': inspection.mobile_local_id,
|
||||
'form_data': form_data,
|
||||
# Absolute display URLs for image form fields (presigned on R2,
|
||||
# absolute-static on local): {field_id: url}. The iPad prefers this
|
||||
# over building ServerConfig + /static/ + value.
|
||||
'form_media': {
|
||||
fid: _media(v)
|
||||
for fid, v in (form_data or {}).items()
|
||||
if isinstance(v, str) and v.startswith('uploads/')
|
||||
},
|
||||
'form_schema': form_schema,
|
||||
'inspector_notes': inspector_notes,
|
||||
# ── Follow-up / re-inspection fields ──────────────────────────────
|
||||
|
||||
@@ -51,6 +51,13 @@ _UUID_RE = re.compile(
|
||||
)
|
||||
|
||||
|
||||
def _photo_urls(keys):
|
||||
"""Map storage keys to absolute display URLs (presigned on R2, absolute-static
|
||||
on local). Falsy keys are skipped. Used for iPad photo rendering."""
|
||||
from app.utils import storage
|
||||
return [storage.media_url(k, external=True) for k in keys if k]
|
||||
|
||||
|
||||
def _issue_payload(issue):
|
||||
"""Serialise an Issue to the dict returned in list/detail responses."""
|
||||
facility = issue.resolved_facility
|
||||
@@ -71,6 +78,14 @@ def _issue_payload(issue):
|
||||
'photo_path': issue.photo_path or None,
|
||||
'mobile_photo_paths': issue.mobile_photo_paths or [],
|
||||
'result_photos': issue.result_photos or [],
|
||||
# Absolute display URLs (presigned on R2, absolute-static on local) for
|
||||
# the iPad, which loads photos off-origin. Relative keys above stay as
|
||||
# keys. photo_urls order mirrors the iPad's evidence merge:
|
||||
# [photo_path] + mobile_photo_paths.
|
||||
'photo_urls': _photo_urls(
|
||||
([issue.photo_path] if issue.photo_path else [])
|
||||
+ (issue.mobile_photo_paths or [])),
|
||||
'result_photo_urls': _photo_urls(issue.result_photos or []),
|
||||
# Resolution details — set by web staff after fixing the issue.
|
||||
'result_notes': issue.result_notes or None,
|
||||
# Verification fields — set after a director/admin confirms fix.
|
||||
|
||||
@@ -76,10 +76,12 @@ class LocalBackend:
|
||||
def abs_local_path(self, key):
|
||||
return os.path.normpath(os.path.join(self._static_folder(), key))
|
||||
|
||||
def media_url(self, key):
|
||||
def media_url(self, key, external=False):
|
||||
if not key:
|
||||
return ''
|
||||
return url_for('static', filename=key)
|
||||
# external=True yields an absolute URL (scheme+host) for API responses
|
||||
# consumed off-origin (the iPad); templates call with external=False.
|
||||
return url_for('static', filename=key, _external=external)
|
||||
|
||||
def read(self, key):
|
||||
with open(self.abs_local_path(key), 'rb') as fh:
|
||||
@@ -164,13 +166,14 @@ class S3Backend:
|
||||
# not exist) so transition code / callers can still reference it.
|
||||
return self._local.abs_local_path(key)
|
||||
|
||||
def media_url(self, key):
|
||||
def media_url(self, key, external=False):
|
||||
if not key:
|
||||
return ''
|
||||
if self.fallback and not self._head(key):
|
||||
# Object not in R2 yet — serve the local copy during transition.
|
||||
if self._local.exists(key):
|
||||
return self._local.media_url(key)
|
||||
return self._local.media_url(key, external=external)
|
||||
# Presigned R2 URLs are always absolute; the external flag is moot.
|
||||
return self._client.generate_presigned_url(
|
||||
'get_object',
|
||||
Params={'Bucket': self.bucket, 'Key': key},
|
||||
@@ -259,8 +262,8 @@ def save(file_obj, subfolder):
|
||||
return get_backend().save(file_obj, subfolder)
|
||||
|
||||
|
||||
def media_url(key):
|
||||
return get_backend().media_url(key)
|
||||
def media_url(key, external=False):
|
||||
return get_backend().media_url(key, external=external)
|
||||
|
||||
|
||||
def read(key):
|
||||
|
||||
Reference in New Issue
Block a user