File uploads¶
uproot supports file uploads from participants using FileField. Uploaded files are handled as stealth fields: They are not stored in the database automatically, giving you full control over how to process them.
Basic file upload¶
Add a FileField to your page:
class Upload(Page):
fields = dict(
cv=FileField(label="Please upload your curriculum vitæ."),
)
@classmethod
async def handle_stealth_fields(page, player, data):
cv = data["cv"]
contents = await cv.read()
player.file_name = cv.filename
player.file_size = len(contents)
FileField is always treated as a stealth field. The uploaded file is passed to handle_stealth_fields as a Starlette UploadFile object.
The UploadFile object¶
The uploaded file has these attributes:
| Attribute | Type | Description |
|---|---|---|
filename |
str |
Original filename |
size |
int |
File size in bytes |
content_type |
str |
MIME type (e.g., image/png) |
And this method:
| Method | Returns | Description |
|---|---|---|
await file.read() |
bytes |
Read the full file contents |
Storing uploaded files¶
Since uploads are stealth fields, you decide where to store the data. Common approaches:
Save to disk¶
@classmethod
async def handle_stealth_fields(page, player, data):
photo = data["photo"]
if photo.size > 0:
contents = await photo.read()
from pathlib import Path
path = Path("uploads") / (
f"{player.session.name}_{player.name}_{uuid()}"
f"{Path(photo.filename or '').suffix.lower()}"
)
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("wb") as f:
f.write(contents)
player.photo_path = str(path)
Store metadata only¶
@classmethod
async def handle_stealth_fields(page, player, data):
doc = data["document"]
player.doc_name = doc.filename
player.doc_size = doc.size
player.doc_type = doc.content_type
Validating uploads¶
Return error messages from handle_stealth_fields to reject the submission:
@classmethod
async def handle_stealth_fields(page, player, data):
photo = data["photo"]
if photo.size == 0:
return "Please select a file"
if photo.size > 5_000_000:
return "File too large (max 5 MB)"
if photo.content_type not in ("image/png", "image/jpeg"):
return "Only PNG and JPEG files are accepted"
contents = await photo.read()
player.photo_size = len(contents)
Combining uploads with regular fields¶
You can mix file uploads with regular fields on the same page. Regular fields are saved automatically; the file field goes through handle_stealth_fields:
class UploadPage(Page):
fields = dict(
participant_name=StringField(label="Your name"),
photo=FileField(label="Upload a photo"),
)
@classmethod
async def handle_stealth_fields(page, player, data):
photo = data["photo"]
if photo.size > 0:
contents = await photo.read()
player.photo_name = photo.filename
The participant_name field saves to player.participant_name automatically.
player.name is reserved for uproot’s generated player name. The photo
field is handled separately.
Rendering in templates¶
Use the standard field rendering:
Or render all fields at once:
Summary¶
| Feature | Purpose |
|---|---|
FileField(label=...) |
File upload field |
handle_stealth_fields(page, player, data) |
Process uploaded files |
await file.read() |
Get file contents as bytes |
file.filename |
Original filename |
file.size |
File size in bytes |
file.content_type |
MIME type |
| Return a string from handler | Validation error message |