Skip to content

feat: Added FXA profile image#4215

Open
wassafshahzad wants to merge 3 commits into
mozilla:mainfrom
wassafshahzad:wassaf/4017-mozilla-profile-image-re
Open

feat: Added FXA profile image#4215
wassafshahzad wants to merge 3 commits into
mozilla:mainfrom
wassafshahzad:wassaf/4017-mozilla-profile-image-re

Conversation

@wassafshahzad

@wassafshahzad wassafshahzad commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Description

Added FXA avatar scope and the avatar will be loaded on runtime using the user_gravatar_url if available. Also changed the property of UserModel from gravatar_url to avatar_url

Linked Issue

Closes #4017

Screenshot

Profle Image

Caveats

There several caveats with this implementation

  • Users using the Gravatar Image with FXA accounts will have there avatars replaced with FXA one since we prioritize FXA ones. The reason is because we are sending the Gravatar image url back to the template. To prioritize it we will first have to send a http request to the url and if it fails default to FXA.
  • The class rounded on img tag does make the FXA image look a bit boxy, see screenshot.

@wassafshahzad wassafshahzad marked this pull request as ready for review June 11, 2026 12:01

@mathjazz mathjazz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

That update looks good.

I'm concerned about the number of times we hit the DB, because we call avatar_url() from so many places so many times.

Let's give it a test on pontoon.allizom.org. We can also check how it performs before we dive into any optimizations.

@flodolo

flodolo commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

I'm concerned about the number of times we hit the DB, because we call avatar_url() from so many places so many times.

That is one query for each user every time we ask for a group of users? If that's the case, worth prefetch the social account data?

@mathjazz

Copy link
Copy Markdown
Collaborator

That is one query for each user every time we ask for a group of users?

One query for each avatar.

@flodolo

flodolo commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

One query for each avatar.

Yes, but it's one additional query for each user, e.g. when we display a list of suggestions/translations, look at the contributors page?

@mathjazz

Copy link
Copy Markdown
Collaborator

Yes, one query for each avatar (not one for the entire group of avatars on the page).

@wassafshahzad

wassafshahzad commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

One query for each avatar.

Yes, but it's one additional query for each user, e.g. when we display a list of suggestions/translations, look at the contributors page?

Since each user will be rendered with his avatar. We can add another dynamic property to the user model with the name fxa avatar which is a cached property initilized once per user model. Rather than calling the db for every avatar call, we can check the fxa_avatar property and if its not none; return that else follow the defauly path.

@mathjazz

Copy link
Copy Markdown
Collaborator

We could avoid the N+1 by prefetching FXA social accounts with Prefetch(..., to_attr="fxa_accounts"), and have avatar_url() read from that attribute when available.

@wassafshahzad

wassafshahzad commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

We could avoid the N+1 by prefetching FXA social accounts with Prefetch(..., to_attr="fxa_accounts"), and have avatar_url() read from that attribute when available.

@mathjazz, Could we also not save the avatar from the fxa social account and save into fxa_avatar property rather than caching the social account instance, something like this

@cached_property
def fxa_avatar(self):
    fxa = self.social_auth.filter(provider="fxa").first()
    return fxa.extra_data.get("avatar") if fxa else None


@wassafshahzad

Copy link
Copy Markdown
Contributor Author

@mathjazz Which approach would you like me to implement. As soon as I have your approval I can update the PR

@mathjazz

Copy link
Copy Markdown
Collaborator

Hey @wassafshahzad,

I like the idea of using @cached_property from readability perspective.

However, I don't see how this solves the N+1 problem.

For example, on the contributors page, we have:

  • 100 users displayed
  • user.fxa_avatar evaluated once per user
  • 100 social_auth.filter(...).first() queries

Perhaps the ideal solution is to combine both:

@cached_property
def fxa_avatar(self):
    if hasattr(self, "_prefetched_fxa_avatar"):
        return self._prefetched_fxa_avatar

    fxa = self.social_auth.filter(provider="fxa").first()
    return fxa.extra_data.get("avatar") if fxa else None

And then in list views:

users = User.objects.prefetch_related(
    Prefetch(
        "social_auth",
        queryset=SocialAccount.objects.filter(provider="fxa"),
        to_attr="_prefetched_fxa_accounts",
    )
)

@wassafshahzad

Copy link
Copy Markdown
Contributor Author

Hey @wassafshahzad,

I like the idea of using @cached_property from readability perspective.

However, I don't see how this solves the N+1 problem.

For example, on the contributors page, we have:

  • 100 users displayed
  • user.fxa_avatar evaluated once per user
  • 100 social_auth.filter(...).first() queries

Perhaps the ideal solution is to combine both:

@cached_property
def fxa_avatar(self):
    if hasattr(self, "_prefetched_fxa_avatar"):
        return self._prefetched_fxa_avatar

    fxa = self.social_auth.filter(provider="fxa").first()
    return fxa.extra_data.get("avatar") if fxa else None

And then in list views:

users = User.objects.prefetch_related(
    Prefetch(
        "social_auth",
        queryset=SocialAccount.objects.filter(provider="fxa"),
        to_attr="_prefetched_fxa_accounts",
    )
)

I will implement this, but just one question should we not attach the _prefetched_fxa_avatar in the cached property if its not available after getting it ?

@mathjazz

Copy link
Copy Markdown
Collaborator

I will implement this, but just one question should we not attach the _prefetched_fxa_avatar in the cached property if its not available after getting it ?

Yeah, that would make sense!

Comment thread pontoon/base/user_utils.py Outdated

def gravatar_url(user: User, size: int = 88) -> str:
def avatar_url(user: User, size: int = 88) -> str:
fxa_account = SocialAccount.objects.filter(user=user, provider="fxa").first()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This function crashes for me locally, it needs a guard if user.pk is not None:.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, I will add it. Anonymous users might be the reason since there pk is None

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@flodolo Just checked again but Anonymous users don't access avatar_url so I am not use what kind of User would be missing a pk but I have added a check regardless

@wassafshahzad

Copy link
Copy Markdown
Contributor Author

@mathjazz I have added the fxa_avatar function. The reason its not a property is because the User model doc strings say to not monkey patch without good reason. I have also updated most if the User list views if I miss something please to inform me

@mathjazz mathjazz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update.

Why is fxa_avatar() in a different module avatar_url()?

Looks like it's only called once? Why don't we simply drop it and move the code to avatar_url()?

We talked about using cached_property (I'm not sure it would have noticeable performance improvements), but we don't actually use it.

If we want to keep the function separate, a more consistent name would be fxa_avatar_url().

Comment thread pontoon/base/models/user.py Outdated

def fxa_avatar(user: User) -> str | None:
if user.pk is None:
return

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'd be explicit here:

Suggested change
return
return None

@wassafshahzad

Copy link
Copy Markdown
Contributor Author

Thanks for the update.

Why is fxa_avatar() in a different module avatar_url()?

Looks like it's only called once? Why don't we simply drop it and move the code to avatar_url()?

We talked about using cached_property (I'm not sure it would have noticeable performance improvements), but we don't actually use it.

If we want to keep the function separate, a more consistent name would be fxa_avatar_url().

The reason its in a separate module is because it initialy was a cached_property but it will not work with cached_property decorator since add_to_class will bypass set_name. To remedy that we can add that property to the UserModel but the docstring on the UserModel says to avoid that approach. I can move it to user_util but would like to keep it separate and not make avatar_url more complex, they also deal with separate concerns.
I can add @cache to fxa_avatar but we will not gain that much performance

@mathjazz

Copy link
Copy Markdown
Collaborator

OK, let's move it to user_utils and call it fxa_avatar_url().

@wassafshahzad wassafshahzad requested a review from mathjazz June 19, 2026 18:49
@wassafshahzad

Copy link
Copy Markdown
Contributor Author

@mathjazz Done

@mathjazz mathjazz requested a review from functionzz June 22, 2026 18:04
@mathjazz

Copy link
Copy Markdown
Collaborator

Thanks for the update @wassafshahzad, passing this to @functionzz for the final look.

@mathjazz

Copy link
Copy Markdown
Collaborator

@wassafshahzad Could you please rebase?

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.

Add Mozilla Account profile avatar as an alternative to Gravatar

3 participants