From a9f0356d913c0a081c9df0708052db1e4ac629f3 Mon Sep 17 00:00:00 2001 From: Julia Hoang Date: Tue, 14 Jul 2026 13:56:12 -0700 Subject: [PATCH 1/7] feat: implement roles in user model --- config/celery.py | 8 + libraries/tasks.py | 2 + static/css/v3/user-profile-page.css | 4 + templates/v3/posts_list.html | 2 +- templates/v3/user_profile_edit.html | 10 +- users/admin.py | 73 ++- users/forms.py | 27 +- ...ty_user_displayed_profile_role_and_more.py | 151 +++++++ .../0023_profile_role_eligibility_matview.py | 49 ++ users/models.py | 311 ++++++++++++- users/tasks.py | 15 + users/tests/test_profile_role.py | 423 ++++++++++++++++++ users/views.py | 48 +- 13 files changed, 1111 insertions(+), 12 deletions(-) create mode 100644 users/migrations/0022_profileroleeligibility_user_displayed_profile_role_and_more.py create mode 100644 users/migrations/0023_profile_role_eligibility_matview.py create mode 100644 users/tests/test_profile_role.py diff --git a/config/celery.py b/config/celery.py index f999ceb4c..29b5ae913 100644 --- a/config/celery.py +++ b/config/celery.py @@ -137,3 +137,11 @@ def setup_periodic_tasks(sender, **kwargs): crontab(day_of_week="mon", hour=5, minute=15), app.signature("core.tasks.refresh_popular_search_terms"), ) + + # Daily safety refresh of the profile role-eligibility materialized view. + # Eligibility only changes during imports, which enqueue this refresh + # themselves; this is a backstop after the daily import window (7:05/8:05 AM). + sender.add_periodic_task( + crontab(hour=9, minute=0), + app.signature("users.tasks.refresh_profile_role_eligibility"), + ) diff --git a/libraries/tasks.py b/libraries/tasks.py index d740e8b51..a3ff837ad 100644 --- a/libraries/tasks.py +++ b/libraries/tasks.py @@ -217,6 +217,7 @@ def update_authors_and_maintainers(): call_command("update_authors") call_command("update_maintainers") call_command("update_library_version_authors", "--clean") + app.signature("users.tasks.refresh_profile_role_eligibility").apply_async() @app.task @@ -232,6 +233,7 @@ def update_commits(token=None, clean=False, min_version=""): library=library, clean=clean, min_version=min_version ) logger.info("update_commits finished.") + app.signature("users.tasks.refresh_profile_role_eligibility").apply_async() return commits_handled diff --git a/static/css/v3/user-profile-page.css b/static/css/v3/user-profile-page.css index 8919af7ac..6834e41ae 100644 --- a/static/css/v3/user-profile-page.css +++ b/static/css/v3/user-profile-page.css @@ -125,6 +125,10 @@ margin: var(--space-default) 0 } +.user-profile__edit-form { + display: contents; +} + .user-profile__badge-select { padding: 6px; display: flex; diff --git a/templates/v3/posts_list.html b/templates/v3/posts_list.html index 9dceb0882..12a00d168 100644 --- a/templates/v3/posts_list.html +++ b/templates/v3/posts_list.html @@ -38,7 +38,7 @@

Posts

{% if request.user.is_authenticated %} {% with u=request.user %} {% url 'v3-news-create' as create_url %} - {% include 'v3/includes/_user_card.html' with username=u.display_name avatar_url=u.avatar_url badge_name='Bug Catcher' badge_icon_src=u.badge_url member_since=u.year_joined role='Contributor' flag_emoji=u.flag_emoji cta_url=create_url cta_label='Create Post' only %} + {% include 'v3/includes/_user_card.html' with username=u.display_name avatar_url=u.avatar_url badge_name='Bug Catcher' badge_icon_src=u.badge_url member_since=u.year_joined role=u.role flag_emoji=u.flag_emoji cta_url=create_url cta_label='Create Post' only %} {% endwith %} {% else %} {% include 'v3/includes/_user_card.html' with logged_out=True cta_url='#' cta_label='Create Post' only %} diff --git a/templates/v3/user_profile_edit.html b/templates/v3/user_profile_edit.html index 2d3e7c078..a56cdb790 100644 --- a/templates/v3/user_profile_edit.html +++ b/templates/v3/user_profile_edit.html @@ -16,6 +16,9 @@