File Uploads in Lback Framework
Handling file uploads is a fundamental part of many web applications. Lback provides a robust and flexible way to manage file uploads, including validation and secure storage.
—
1. How File Uploads Work in Lback
The file upload process relies on several core components working seamlessly together:
BodyParsingMiddleware: (inlback/middlewares/body_parsing_middleware.py)This middleware is responsible for parsing the request body when the
Content-Typeismultipart/form-data. It transforms uploaded files intoUploadedFileobjects and makes them accessible viarequest.files.
UploadedFileClass: (inlback/core/types.py)This object represents an uploaded file. It holds useful properties such as
filename(the original file name),content_type(MIME type),size(size in bytes), andfile(a file-like object representing the file’s content).
file_handlers.pyUtilities: (inlback/utils/file_handlers.py)This file contains helper functions to streamline file processing: *
validate_uploaded_file(): Checks the file’s type and size against specified rules. *save_uploaded_file(): Saves the uploaded file to a structured directory on the file system. *delete_saved_file(): Deletes a previously saved file.
- Generic Admin Panel (and your custom controllers):
These components (like the Generic Add View or Generic Edit View) leverage the
file_handlersfunctions to automatically process files uploaded from HTML forms.
The Workflow:
When a user submits an HTML form containing an
<input type="file">field, the request is sent asmultipart/form-data.The BodyParsingMiddleware intercepts and parses this request, extracting the uploaded files.
Each uploaded file is converted into an
UploadedFileobject and stored inrequest.files(which is aMultiDict, allowing for multiple files under the same field name).In your View or Controller (whether it’s a Generic Admin View or your custom view): * You can access uploaded files via request.files. * You can call
validate_uploaded_file()to check the file’s integrity before saving. * You can callsave_uploaded_file()to store the file in the correct location on the server. * The saved file paths (relative paths) are what you should store in your database.
—
2. File Upload Configuration Options in settings.py
You can customize file upload behavior by configuring the following settings in your project’s settings.py file:
- UPLOAD_FOLDER: (Optional, default is
uploads) The name of the base directory within your project where all uploaded files will be stored. Example:
UPLOAD_FOLDER = 'static/uploads'
- UPLOAD_FOLDER: (Optional, default is
- UPLOAD_ALLOWED_TYPES: (Optional, default is
Nonewhich allows all types) A list of allowed MIME types for uploaded files. Example:
UPLOAD_ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf']
- UPLOAD_ALLOWED_TYPES: (Optional, default is
- UPLOAD_MAX_SIZE_MB: (Optional, default is
Nonefor no size limit) The maximum allowed size for uploaded files in megabytes. Example:
UPLOAD_MAX_SIZE_MB = 5(maximum size of 5 MB)
- UPLOAD_MAX_SIZE_MB: (Optional, default is
—
3. How to Handle Uploaded Files in Views
Once BodyParsingMiddleware parses the request, uploaded files will be available in request.files as UploadedFile objects.
#### Examples:
Accessing a Single Uploaded File: If you expect a single file for a specific field (e.g.,
profile_picture):# In your View or Controller from lback.core.types import Request, UploadedFile from lback.utils.file_handlers import save_uploaded_file, validate_uploaded_file from lback.core.config import Config # Ensure Config or current_app.config is imported def upload_profile_picture(request: Request): # Assume you have a Config object available here, perhaps from request.app.config config: Config = request.app.config # Or the appropriate way to access app configuration # Access the uploaded file profile_pic_file: Optional[UploadedFile] = request.files.get('profile_picture') if profile_pic_file: # Pass the `model_name` under which you want to save the files (typically the model/entity name) # and the validation settings defined in your config saved_path = save_uploaded_file( profile_pic_file, config=config, model_name='users', # E.g., 'users' folder within UPLOAD_FOLDER allowed_types=config.UPLOAD_ALLOWED_TYPES, max_size_mb=config.UPLOAD_MAX_SIZE_MB ) if saved_path: # File saved successfully! # You can now store 'saved_path' in your database (e.g., 'profile_picture_path' column in the User table) # user.profile_picture_path = saved_path # db_session.add(user) # db_session.commit() request.session['flash_messages'].append({'message': f"Profile picture uploaded successfully: {saved_path}", 'category': 'success'}) return redirect('/profile') else: # Save failed, check for validation errors error_message = validate_uploaded_file(profile_pic_file, config.UPLOAD_ALLOWED_TYPES, config.UPLOAD_MAX_SIZE_MB) if not error_message: # If no specific validation error, the error was during the save operation error_message = "Failed to save profile picture due to an unexpected error." request.session['flash_messages'].append({'message': error_message, 'category': 'error'}) return redirect('/upload-form') else: request.session['flash_messages'].append({'message': 'No profile picture file was provided.', 'category': 'warning'}) return redirect('/upload-form')
Handling Multiple Files for the Same Field (
multipleattribute in HTML): If you have a multi-file input field (e.g.,gallery_images[]):# In your View or Controller from typing import List # ... (same previous imports) ... def upload_gallery_images(request: Request): config: Config = request.app.config # request.files.getlist() returns a list of objects for a given field name gallery_files: List[UploadedFile] = request.files.getlist('gallery_images') uploaded_paths = [] errors = [] if gallery_files: for file_obj in gallery_files: if isinstance(file_obj, UploadedFile): saved_path = save_uploaded_file( file_obj, config=config, model_name='gallery', # 'gallery' folder allowed_types=config.UPLOAD_ALLOWED_TYPES, max_size_mb=config.UPLOAD_MAX_SIZE_MB ) if saved_path: uploaded_paths.append(saved_path) # Now you can save 'saved_path' to your database, perhaps in an Image table related to Gallery else: error_message = validate_uploaded_file(file_obj, config.UPLOAD_ALLOWED_TYPES, config.UPLOAD_MAX_SIZE_MB) if not error_message: error_message = f"Failed to save {file_obj.filename} due to an unexpected error." errors.append(f"Error with {file_obj.filename}: {error_message}") else: errors.append(f"Unexpected file object type for {file_obj.filename}.") else: errors.append("No gallery images were provided.") if uploaded_paths: request.session['flash_messages'].append({'message': f"Successfully uploaded {len(uploaded_paths)} images.", 'category': 'success'}) if errors: for err in errors: request.session['flash_messages'].append({'message': err, 'category': 'error'}) return redirect('/gallery')
Deleting a Saved File:
# In your View or Controller from lback.utils.file_handlers import delete_saved_file # ... (same previous imports) ... def delete_image(request: Request): image_relative_path = request.form.get('image_path') # Assume the path comes from the form config: Config = request.app.config if image_relative_path: if delete_saved_file(image_relative_path, config=config): # File successfully deleted from the file system # Now, delete the path from your database # image_record = db_session.query(Image).filter_by(path=image_relative_path).first() # if image_record: # db_session.delete(image_record) # db_session.commit() request.session['flash_messages'].append({'message': "Image deleted successfully.", 'category': 'success'}) return redirect('/admin/images') else: request.session['flash_messages'].append({'message': "Failed to delete image from server.", 'category': 'error'}) return redirect('/admin/images') else: request.session['flash_messages'].append({'message': "No image path provided for deletion.", 'category': 'warning'}) return redirect('/admin/images')