Skip to content

Commit 96ecbcd

Browse files
Display info about image constraints on frontend
1 parent b453116 commit 96ecbcd

File tree

6 files changed

+28
-11
lines changed

6 files changed

+28
-11
lines changed

docs/installation.qmd

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@ Before running the development server, make sure the development database is run
165165
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
166166
```
167167

168-
Navigate to http://localhost:8000/
168+
Navigate to http://localhost:8000/.
169+
170+
(Note: If startup fails with a sqlalchemy/psycopg2 connection error, make sure that Docker Desktop and the database service are running and that the environment variables in the `.env` file are correctly populated, and then try again.)
169171

170172
## Lint types with mypy
171173

main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from utils.auth import get_user_with_relations, get_optional_user, NeedsNewTokens, get_user_from_reset_token, PasswordValidationError, AuthenticationError
1212
from utils.models import User, Organization
1313
from utils.db import get_session, set_up_db
14+
from utils.images import MAX_FILE_SIZE, MIN_DIMENSION, MAX_DIMENSION, ALLOWED_CONTENT_TYPES
1415

1516
logger = logging.getLogger("uvicorn.error")
1617
logger.setLevel(logging.DEBUG)
@@ -246,6 +247,13 @@ async def read_dashboard(
246247
async def read_profile(
247248
params: dict = Depends(common_authenticated_parameters)
248249
):
250+
# Add image constraints to the template context
251+
params.update({
252+
"max_file_size_mb": MAX_FILE_SIZE / (1024 * 1024), # Convert bytes to MB
253+
"min_dimension": MIN_DIMENSION,
254+
"max_dimension": MAX_DIMENSION,
255+
"allowed_formats": list(ALLOWED_CONTENT_TYPES.keys())
256+
})
249257
return templates.TemplateResponse(params["request"], "users/profile.html", params)
250258

251259

templates/authentication/register.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<!-- Password Input -->
2525
<div class="mb-3">
2626
<label for="password" class="form-label">Password</label>
27-
<!-- Make sure 9g,X*88w[6"W passes validation -->
27+
<!-- Make sure 9g,X*88w[6"W and ^94cPSf2^)z2^,& pass validation -->
2828
<input type="password" class="form-control" id="password" name="password"
2929
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@$!%*?&\{\}\<\>\.\,\\\\'#\-_=\+\(\)\[\]:;\|~\/])[A-Za-z\d@$!%*?&\{\}\<\>\.\,\\\\'#\-_=\+\(\)\[\]:;\|~\/]{8,}"
3030
title="Must contain at least one number, one uppercase and lowercase letter, one special character, and at least 8 or more characters"

templates/users/profile.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ <h1 class="mb-4">User Profile</h1>
4747
<div class="mb-3">
4848
<label for="avatar_file" class="form-label">Avatar</label>
4949
<input type="file" class="form-control" id="avatar_file" name="avatar_file" accept="image/*">
50+
<div class="form-text">
51+
<ul class="mb-0">
52+
<li>Maximum file size: {{ max_file_size_mb }} MB</li>
53+
<li>Minimum dimension: {{ min_dimension }}x{{ min_dimension }} pixels</li>
54+
<li>Maximum dimension: {{ max_dimension }}x{{ max_dimension }} pixels</li>
55+
<li>Allowed formats: {{ allowed_formats|join(', ') }}</li>
56+
<li>Image will be cropped to a square</li>
57+
</ul>
58+
</div>
5059
</div>
5160
<button type="submit" class="btn btn-primary">Save Changes</button>
5261
</form>

tests/test_images.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
InvalidImageError,
77
MAX_FILE_SIZE,
88
MIN_DIMENSION,
9-
MAX_DIMENSION,
10-
TARGET_SIZE
9+
MAX_DIMENSION
1110
)
1211

1312
def create_test_image(width: int, height: int, format: str = 'PNG') -> bytes:
@@ -28,7 +27,7 @@ def test_valid_square_image():
2827

2928
# Verify the processed image
3029
processed_image = Image.open(io.BytesIO(processed_data))
31-
assert processed_image.size == (TARGET_SIZE, TARGET_SIZE)
30+
assert processed_image.size == (500, 500)
3231
assert content_type == 'image/png'
3332

3433
def test_valid_rectangular_image():
@@ -38,7 +37,7 @@ def test_valid_rectangular_image():
3837

3938
# Verify the processed image
4039
processed_image = Image.open(io.BytesIO(processed_data))
41-
assert processed_image.size == (TARGET_SIZE, TARGET_SIZE)
40+
assert processed_image.size == (600, 600)
4241
assert content_type == 'image/png'
4342

4443
def test_minimum_size_image():
@@ -47,7 +46,7 @@ def test_minimum_size_image():
4746
processed_data, content_type = validate_and_process_image(image_data, 'image/png')
4847

4948
processed_image = Image.open(io.BytesIO(processed_data))
50-
assert processed_image.size == (TARGET_SIZE, TARGET_SIZE)
49+
assert processed_image.size == (100, 100)
5150

5251
def test_too_small_image():
5352
"""Test that too small images are rejected"""
@@ -99,6 +98,6 @@ def test_different_image_formats():
9998

10099
# Verify the processed image
101100
processed_image = Image.open(io.BytesIO(processed_data))
102-
assert processed_image.size == (TARGET_SIZE, TARGET_SIZE)
101+
assert processed_image.size == (500, 500)
103102
# Output should match input format
104103
assert result_type == content_type

utils/images.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
}
1414
MIN_DIMENSION = 100
1515
MAX_DIMENSION = 2000
16-
TARGET_SIZE = 500
1716

1817

1918
class InvalidImageError(HTTPException):
@@ -30,6 +29,7 @@ def validate_and_process_image(
3029
"""
3130
Validates and processes an image file.
3231
Returns a tuple of (processed_image_data, content_type).
32+
Ensures the image is square by center-cropping.
3333
3434
Raises:
3535
InvalidImageError: If the image is invalid or doesn't meet requirements
@@ -67,15 +67,14 @@ def validate_and_process_image(
6767
message=f"Image too large. Maximum dimension is {MAX_DIMENSION}px"
6868
)
6969

70-
# Crop to square and resize
70+
# Crop to square
7171
min_dim = min(width, height)
7272
left = (width - min_dim) // 2
7373
top = (height - min_dim) // 2
7474
right = left + min_dim
7575
bottom = top + min_dim
7676

7777
image = image.crop((left, top, right, bottom))
78-
image = image.resize((TARGET_SIZE, TARGET_SIZE), Image.Resampling.LANCZOS)
7978

8079
# Get the format from the content type
8180
output_format = ALLOWED_CONTENT_TYPES[content_type]

0 commit comments

Comments
 (0)