Add files via upload

This commit is contained in:
Joshua Hare 2025-05-11 20:35:32 +10:00 committed by GitHub
parent a2b827d02f
commit bb7016cb8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 62 additions and 44 deletions

View File

@ -8,4 +8,4 @@ COPY instance/ instance/
COPY config.py .
COPY .env .
EXPOSE 5009
CMD ["flask", "run", "--host=0.0.0.0" "--port=5009"]
CMD ["flask", "run", "--host=0.0.0.0", "--port=5009"]

View File

@ -1,5 +1,5 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from flask_wtf.file import FileField, FileAllowed, MultipleFileField
from wtforms import StringField, TextAreaField, SubmitField, PasswordField, SelectField, SelectMultipleField, BooleanField
from wtforms.validators import DataRequired, Email, Length, EqualTo, Optional, ValidationError
import re
@ -9,10 +9,12 @@ from app import db
def validate_url_or_path(form, field):
if not field.data:
return
if field.data.startswith('/app/uploads/'):
path_pattern = r'^/app/uploads/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}_[\w\-\.]+\.(jpg|png)$'
# Allow upload paths like /uploads/<uuid>_<filename>.jpg|png
if field.data.startswith('/uploads/'):
path_pattern = r'^/uploads/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}_[\w\-\.]+\.(jpg|png)$'
if re.match(path_pattern, field.data, re.I):
return
# Allow external URLs
url_pattern = r'^(https?:\/\/)?([\w\-]+\.)+[\w\-]+(\/[\w\-\.]*)*\/?(\?[^\s]*)?(#[^\s]*)?$'
if not re.match(url_pattern, field.data):
raise ValidationError('Invalid URL or file path.')
@ -44,7 +46,7 @@ class FHIRAppForm(FlaskForm):
licensing_pricing = SelectField('Licensing & Pricing', coerce=int, validators=[DataRequired()])
os_support = SelectMultipleField('OS Support', coerce=int, validators=[DataRequired()])
app_image_urls = TextAreaField('App Image URLs (one per line)', validators=[Optional(), Length(max=1000)], render_kw={"placeholder": "e.g., https://example.com/image1.png"})
app_image_uploads = FileField('Upload App Images', validators=[FileAllowed(['jpg', 'png'], 'Images only!')])
app_image_uploads = MultipleFileField('Upload App Images', validators=[FileAllowed(['jpg', 'png'], 'Images only!')])
submit = SubmitField('Register App')
def __init__(self, *args, **kwargs):

View File

@ -154,25 +154,24 @@ def register():
if form.app_image_urls.data:
app_images.extend([url.strip() for url in form.app_image_urls.data.splitlines() if url.strip().startswith(('http://', 'https://'))])
if form.app_image_uploads.data:
file = form.app_image_uploads.data
if file and allowed_file(file.filename):
filename = secure_filename(f"{uuid.uuid4()}_{file.filename}")
(my apologies, I did not mean to interrupt you, please go ahead and continue.
save_path = os.path.join(UPLOAD_FOLDER, filename)
logger.debug(f"Attempting to save app image to {save_path}")
try:
file.save(save_path)
if os.path.exists(save_path):
logger.debug(f"Successfully saved app image to {save_path}")
else:
logger.error(f"Failed to save app image to {save_path}")
flash('Failed to save app image.', 'danger')
for file in form.app_image_uploads.data:
if file and allowed_file(file.filename):
filename = secure_filename(f"{uuid.uuid4()}_{file.filename}")
save_path = os.path.join(UPLOAD_FOLDER, filename)
logger.debug(f"Attempting to save app image to {save_path}")
try:
file.save(save_path)
if os.path.exists(save_path):
logger.debug(f"Successfully saved app image to {save_path}")
else:
logger.error(f"Failed to save app image to {save_path}")
flash('Failed to save app image.', 'danger')
return render_template('register.html', form=form)
app_images.append(f"/uploads/{filename}")
except Exception as e:
logger.error(f"Error saving app image to {save_path}: {e}")
flash('Error saving app image.', 'danger')
return render_template('register.html', form=form)
app_images.append(f"/uploads/{filename}")
except Exception as e:
logger.error(f"Error saving app image to {save_path}: {e}")
flash('Error saving app image.', 'danger')
return render_template('register.html', form=form)
app = FHIRApp(
name=form.name.data,
@ -258,24 +257,24 @@ def edit_app(app_id):
app_images = [url.strip() for url in form.app_image_urls.data.splitlines() if url.strip()]
if form.app_image_uploads.data:
file = form.app_image_uploads.data
if file and allowed_file(file.filename):
filename = secure_filename(f"{uuid.uuid4()}_{file.filename}")
save_path = os.path.join(UPLOAD_FOLDER, filename)
logger.debug(f"Attempting to save updated app image to {save_path}")
try:
file.save(save_path)
if os.path.exists(save_path):
logger.debug(f"Successfully saved updated app image to {save_path}")
else:
logger.error(f"Failed to save updated app image to {save_path}")
flash('Failed to save app image.', 'danger')
for file in form.app_image_uploads.data:
if file and allowed_file(file.filename):
filename = secure_filename(f"{uuid.uuid4()}_{file.filename}")
save_path = os.path.join(UPLOAD_FOLDER, filename)
logger.debug(f"Attempting to save updated app image to {save_path}")
try:
file.save(save_path)
if os.path.exists(save_path):
logger.debug(f"Successfully saved updated app image to {save_path}")
else:
logger.error(f"Failed to save updated app image to {save_path}")
flash('Failed to save app image.', 'danger')
return render_template('edit_app.html', form=form, app=app)
app_images.append(f"/uploads/{filename}")
except Exception as e:
logger.error(f"Error saving updated app image to {save_path}: {e}")
flash('Error saving app image.', 'danger')
return render_template('edit_app.html', form=form, app=app)
app_images.append(f"/uploads/{filename}")
except Exception as e:
logger.error(f"Error saving updated app image to {save_path}: {e}")
flash('Error saving app image.', 'danger')
return render_template('edit_app.html', form=form, app=app)
app.name = form.name.data
app.description = form.description.data

View File

@ -114,7 +114,8 @@
</div>
<div class="mb-3">
{{ form.app_image_uploads.label(class="form-label") }}
{{ form.app_image_uploads(class="form-control") }}
{{ form.app_image_uploads(class="form-control", multiple=True) }}
<small class="form-text text-muted">Select multiple images to upload.</small>
{% for error in form.app_image_uploads.errors %}
<span class="text-danger">{{ error }}</span>
{% endfor %}

View File

@ -124,7 +124,7 @@
<div class="app-item col-md-4 mb-3" style="animation: fadeIn 0.5s;">
<div class="card app-card shadow-sm h-100">
{% if app.logo_url %}
<img src="{{ app.logo_url }}" class="card-img-top" alt="{{ app.name }} logo" style="max-height: 150px; object-fit: contain; padding: 1rem;" onerror="this.style.display='none';">
<img src="{{ app.logo_url }}" class="card-img-top" alt="{{ app.name }} logo" style="max-height: 150px; object-fit: contain; padding: 1rem;" onerror="this.src='https://via.placeholder.com/150?text=No+Logo';">
{% else %}
<img src="https://via.placeholder.com/150?text=No+Logo" class="card-img-top" alt="No Logo" style="max-height: 150px; object-fit: contain; padding: 1rem;">
{% endif %}
@ -132,6 +132,14 @@
<h5 class="card-title">{{ app.name }}</h5>
<p class="card-text flex-grow-1">{{ app.description | truncate(100) }}</p>
<p class="card-text"><small class="text-muted">By {{ app.developer }}</small></p>
<div class="d-flex flex-wrap gap-2 mb-2">
{% if app.website %}
<a href="{{ app.website }}" class="btn btn-primary btn-sm" aria-label="Visit {{ app.name }} website">Website</a>
{% endif %}
{% if app.launch_url %}
<a href="{{ app.launch_url }}" class="btn btn-outline-primary btn-sm" aria-label="Try {{ app.name }} with sandbox data">Try App</a>
{% endif %}
</div>
<a href="{{ url_for('gallery.app_detail', app_id=app.id) }}" class="btn btn-primary mt-auto">View Details</a>
</div>
</div>

View File

@ -18,7 +18,7 @@
<div class="col-md-4 mb-3">
<div class="card app-card shadow-sm h-100">
{% if app.logo_url %}
<img src="{{ app.logo_url }}" class="card-img-top" alt="{{ app.name }} logo" style="max-height: 150px; object-fit: contain; padding: 1rem;">
<img src="{{ app.logo_url }}" class="card-img-top" alt="{{ app.name }} logo" style="max-height: 150px; object-fit: contain; padding: 1rem;" onerror="this.src='https://via.placeholder.com/150?text=No+Logo';">
{% else %}
<img src="https://via.placeholder.com/150?text=No+Logo" class="card-img-top" alt="No Logo" style="max-height: 150px; object-fit: contain; padding: 1rem;">
{% endif %}
@ -26,6 +26,14 @@
<h5 class="card-title">{{ app.name }}</h5>
<p class="card-text flex-grow-1">{{ app.description | truncate(100) }}</p>
<p class="card-text"><small class="text-muted">By {{ app.developer }}</small></p>
<div class="d-flex flex-wrap gap-2 mb-2">
{% if app.website %}
<a href="{{ app.website }}" class="btn btn-primary btn-sm" aria-label="Visit {{ app.name }} website">Website</a>
{% endif %}
{% if app.launch_url %}
<a href="{{ app.launch_url }}" class="btn btn-outline-primary btn-sm" aria-label="Try {{ app.name }} with sandbox data">Try App</a>
{% endif %}
</div>
<a href="{{ url_for('gallery.app_detail', app_id=app.id) }}" class="btn btn-primary mt-auto">View Details</a>
</div>
</div>

View File

@ -10,4 +10,4 @@ services:
environment:
- FLASK_APP=app
- FLASK_ENV=development
command: ["flask", "run", "--host=0.0.0.0"]
command: ["flask", "run", "--host=0.0.0.0", "--port=5009"]