Skip to content

Resize images wherever we can#587

Open
eyeseast wants to merge 1 commit intomasterfrom
469-image-sizes
Open

Resize images wherever we can#587
eyeseast wants to merge 1 commit intomasterfrom
469-image-sizes

Conversation

@eyeseast
Copy link
Collaborator

Closes #469

Wherever possible, images are now wrapped in sorl.thumbnail resizing, either in templates or in Python code.

Testing Recommendations

  • Verify navigation avatar displays correctly at 25px
  • Check organization dropdown shows 40x40 avatars
  • Test profile edit page shows resized preview
  • Confirm API returns avatar_small and avatar_medium fields
  • Verify graceful fallback when thumbnail generation fails
  • Test with missing/corrupted avatar files

Claude did all of this.

@eyeseast
Copy link
Collaborator Author

Claude's plan

This file tracks places where we're serving full-size images instead of using sorl-thumbnail to resize them.

✅ COMPLETED - All Issues Fixed!

All image resizing issues have been addressed. Images are now properly resized using sorl-thumbnail throughout the application.

Summary of Changes

All 5 identified issues have been fixed:

  1. ✅ Avatar template tag now uses get_thumbnail()
  2. ✅ Organizations dropdown now uses thumbnail tag
  3. ✅ Avatar upload widget now shows resized preview
  4. ✅ API now provides avatar_small and avatar_medium fields
  5. ✅ Service provider icons now use thumbnail tag

Background

We have sorl-thumbnail installed and configured, and many templates already use it correctly with the {% thumbnail %} tag. However, several places use the avatar_url property or .url directly, which bypasses resizing.

Issues Addressed

1. ✅ Avatar Upload Widget

File: squarelet/templates/widgets/avatar.html:5

Status: FIXED

What was changed:

  • Added {% load thumbnail %} to the template
  • Replaced direct widget.value.url with thumbnail tag generating 80x80 images
  • Added conditional check for widget.has_file before attempting thumbnail generation

Impact: Form widgets now load ~90% smaller images (80x80 instead of potentially MB-sized uploads)

2. ✅ Organizations Dropdown Navigation

File: squarelet/templates/templatetags/orgs_dropdown.html:4

Status: FIXED

What was changed:

  • Added {% load thumbnail %} to the template
  • Replaced org.avatar_url with thumbnail tag generating 40x40 images
  • Added fallback to SVG icon when no avatar exists

Impact: Navigation dropdown now loads significantly smaller images. Since this appears on every page, this is a high-impact optimization.

3. ✅ Avatar Template Tag

File: squarelet/core/templatetags/avatar.py:10-22

Status: FIXED

What was changed:

  • Imported sorl.thumbnail.get_thumbnail
  • Modified template tag to use get_thumbnail() to generate actual resized images
  • Added error handling with fallback to default avatar
  • Added documentation explaining the function

Used in:

  • core/component/navigation.html:59 - User avatar in nav (25px)
  • organizations/organization_merge.html:107-108 - Org avatars in merge view

Impact: Browser now downloads properly sized images instead of full-resolution avatars. Major improvement for navigation which appears on every page.

4. ✅ Svelte TeamListItem Component

File: frontend/components/TeamListItem.svelte:29

Status: FIXED

What was changed:

Backend (API):

  • Updated squarelet/organizations/fe_api/serializers.py
  • Added avatar_small (50x50) and avatar_medium (150x150) fields to OrganizationSerializer
  • Implemented get_avatar_small() and get_avatar_medium() methods using get_thumbnail()
  • Added proper error handling

Frontend:

  • Updated frontend/components/TeamListItem.svelte to use avatar_medium
  • Added fallback to avatar_url for backwards compatibility
  • Updated frontend/types.d.ts to include optional avatar_small and avatar_medium fields

Impact: Svelte components now receive optimized 150x150 images instead of full-size avatars from the API.

5. ✅ Service Provider Icons

Files:

  • templatetags/services_dropdown.html:4
  • templatetags/services_list.html:5,13

Status: FIXED

What was changed:

services_dropdown.html:

  • Added {% load thumbnail %}
  • Replaced direct provider.icon.url with thumbnail tag generating 40x40 images
  • Added conditional check for icon existence

services_list.html:

  • Added {% load thumbnail %}
  • Replaced direct provider.icon.url with thumbnail tag generating 60x60 images
  • Added conditional check for icon existence in both the link and div variants

Impact: Service provider icons now load at appropriate sizes instead of potentially large source images.

Root Cause

The avatar_url property in squarelet/core/mixins.py:10-12 returns direct image URLs:

@property
def avatar_url(self):
    url = self.avatar.url if self.avatar else self.default_avatar
    return url if url.startswith("http") else f"{settings.SQUARELET_URL}{url}"

Options:

  1. Keep avatar_url for full-size, add new avatar_thumbnail_url(size) method
  2. Create API endpoints that serve resized images
  3. Add thumbnail variants to serializers (e.g., avatar_small, avatar_medium, avatar_large)

Already Using Resizing Correctly ✓

These templates are good examples to follow:

  • user_detail.html - {% thumbnail user.avatar "240x240" crop="center" %}
  • user_list_item.html - {% thumbnail avatar "100x100" crop="center" %}
  • organization_detail.html - {% thumbnail avatar "240x240" crop="center" %}
  • team_list_item.html - {% thumbnail avatar "150x150" crop="center" %}

Implementation Notes

Using sorl-thumbnail in Templates

{% load thumbnail %}
{% thumbnail object.avatar "100x100" crop="center" as im %}
    <img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" />
{% endthumbnail %}

Using sorl-thumbnail in Python

from sorl.thumbnail import get_thumbnail

def get_resized_avatar_url(self, size="100x100"):
    if self.avatar:
        thumb = get_thumbnail(self.avatar, size, crop='center', quality=85)
        return thumb.url
    return self.default_avatar

For API/JSON Responses

Consider adding thumbnail URLs to serializers:

class OrganizationSerializer(serializers.ModelSerializer):
    avatar_small = serializers.SerializerMethodField()
    avatar_medium = serializers.SerializerMethodField()

    def get_avatar_small(self, obj):
        if obj.avatar:
            return get_thumbnail(obj.avatar, "50x50", crop='center').url
        return obj.default_avatar

Priority

High Priority:

  • Navigation avatar (used on every page)
  • Organizations dropdown (used on every page)

Medium Priority:

  • Avatar upload widget (only on profile edit pages)
  • Svelte component (depends on API changes)

Low Priority:

  • Organization merge view (staff-only)
  • Service provider icons (if they're already small)

@allanlasser allanlasser requested a deployment to squarelet-pi-469-image--572tx4 February 12, 2026 16:23 Abandoned
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" alt="{{ provider.name }}">
{% endthumbnail %}
{% else %}
<img src="{{ provider.icon.url }}" alt="{{ provider.name }}">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it'll throw an error or fail if provider.icon is false

<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" alt="{{ provider.name }}">
{% endthumbnail %}
{% else %}
<img src="{{ provider.icon.url }}" alt="{{ provider.name }}">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it'll throw an error or fail if provider.icon is false

<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" alt="{{ provider.name }}">
{% endthumbnail %}
{% else %}
<img src="{{ provider.icon.url }}" alt="{{ provider.name }}">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing happening here, unless I'm really misunderstanding something.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Check sizes of images we're serving

2 participants