feat: Added FXA profile image#4215
Conversation
mathjazz
left a comment
There was a problem hiding this comment.
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.
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? |
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? |
|
Yes, one query for each avatar (not one for the entire group of avatars on the 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. |
|
We could avoid the N+1 by prefetching FXA social accounts with |
@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 |
|
@mathjazz Which approach would you like me to implement. As soon as I have your approval I can update the PR |
|
Hey @wassafshahzad, I like the idea of using However, I don't see how this solves the N+1 problem. For example, on the contributors page, we have:
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 NoneAnd 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 ? |
Yeah, that would make sense! |
|
|
||
| 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() |
There was a problem hiding this comment.
This function crashes for me locally, it needs a guard if user.pk is not None:.
There was a problem hiding this comment.
Yes, I will add it. Anonymous users might be the reason since there pk is None
There was a problem hiding this comment.
@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
|
@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
left a comment
There was a problem hiding this comment.
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().
|
|
||
| def fxa_avatar(user: User) -> str | None: | ||
| if user.pk is None: | ||
| return |
There was a problem hiding this comment.
I'd be explicit here:
| return | |
| return None |
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. |
|
OK, let's move it to |
|
@mathjazz Done |
|
Thanks for the update @wassafshahzad, passing this to @functionzz for the final look. |
|
@wassafshahzad Could you please rebase? |
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
Caveats
There several caveats with this implementation