Coverage for app/backend/src/couchers/helpers/completed_profile.py: 100%

13 statements  

« prev     ^ index     » next       coverage.py v7.15.3, created at 2026-08-04 22:32 +0000

1from sqlalchemy import and_, select 

2from sqlalchemy.orm import Session 

3from sqlalchemy.sql.elements import ColumnElement 

4from sqlalchemy.sql.selectable import Subquery 

5 

6from couchers.constants import COMPLETED_PROFILE_MINIMUM_CHAR_LENGTH 

7from couchers.models import User 

8from couchers.models.uploads import has_avatar_photo_expression 

9 

10 

11def has_completed_profile(session: Session, user: User) -> bool: 

12 """ 

13 Check if a user has completed their profile (has photo + 150 char about_me). 

14 """ 

15 if not user.profile_gallery_id or not user.about_me or len(user.about_me) < COMPLETED_PROFILE_MINIMUM_CHAR_LENGTH: 

16 return False 

17 return bool(session.execute(select(has_avatar_photo_expression(user))).scalar()) 

18 

19 

20def has_completed_profile_expression(galleries_with_photos: Subquery | None = None) -> ColumnElement[bool]: 

21 """ 

22 Returns a SQL expression for checking if a user has completed their profile. 

23 

24 Use this in SQLAlchemy queries where you need to filter by profile completeness. 

25 

26 The avatar is checked with a correlated EXISTS, which the planner flattens into a semi-join in a WHERE clause. 

27 Where it can't do that -- inside an aggregate FILTER clause it stays a subplan and runs once per row -- pass a 

28 subquery of distinct photo_gallery_items.gallery_id that the statement outer joins on User.profile_gallery_id, 

29 and the check reads that join instead. 

30 

31 Usage: 

32 statement = select(User).where(has_completed_profile_expression()) 

33 """ 

34 return and_( 

35 User.profile_gallery_id != None, 

36 has_avatar_photo_expression(User) 

37 if galleries_with_photos is None 

38 else galleries_with_photos.c.gallery_id.isnot(None), 

39 User.about_me_length >= COMPLETED_PROFILE_MINIMUM_CHAR_LENGTH, 

40 )