From 0a8235e207f4deb60f637f6adb9baa8000553203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C4=8Ce=C5=A1ka?= Date: Tue, 29 Aug 2023 13:29:00 +0200 Subject: [PATCH 1/7] profile completeness progress bar --- fiesta/apps/accounts/templatetags/user_profile.py | 12 ++++++++---- fiesta/apps/utils/templatetags/utils.py | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/fiesta/apps/accounts/templatetags/user_profile.py b/fiesta/apps/accounts/templatetags/user_profile.py index 9203e998..b0acd8f5 100644 --- a/fiesta/apps/accounts/templatetags/user_profile.py +++ b/fiesta/apps/accounts/templatetags/user_profile.py @@ -18,11 +18,15 @@ def get_user_picture(user: User | None): return profile.picture - @register.simple_tag(takes_context=True) def compute_profile_fullness(context: dict, profile: UserProfile) -> float: - # req: HttpRequest = context.get("request") + fields = profile._meta.get_fields() # Get all field names of UserProfile + empty_fields = 0 + + for field in fields: + field_value = getattr(profile, field.name, None) + if field_value is None or field_value == '': + empty_fields += 1 - # TODO: compute based on accounts conf and profile state + return (len(fields) - empty_fields) / len(fields) - return 0.77 diff --git a/fiesta/apps/utils/templatetags/utils.py b/fiesta/apps/utils/templatetags/utils.py index 41028b8f..02269560 100644 --- a/fiesta/apps/utils/templatetags/utils.py +++ b/fiesta/apps/utils/templatetags/utils.py @@ -37,7 +37,7 @@ def map_attrgetter(iterable: Reversible, attr: str): @register.simple_tag def interpolate_to_list(value: float, *values: typing.Any): - return values[int(min(1, max(0, value)) * len(values))] + return values[int(min(1, max(0, value)) * len(values)) - 1] @register.filter From e58e983d191d60a0c1ef20d888fdd0b77406e46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C4=8Ce=C5=A1ka?= Date: Tue, 5 Sep 2023 11:27:52 +0200 Subject: [PATCH 2/7] profile completeness progress bar computed only from fields available to user --- fiesta/apps/accounts/forms/profile.py | 48 +++++++++++-------- .../templates/accounts/dashboard_block.html | 2 +- .../accounts/templatetags/user_profile.py | 9 ++-- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/fiesta/apps/accounts/forms/profile.py b/fiesta/apps/accounts/forms/profile.py index efeceb53..6c21bcde 100644 --- a/fiesta/apps/accounts/forms/profile.py +++ b/fiesta/apps/accounts/forms/profile.py @@ -10,7 +10,6 @@ from apps.fiestaforms.widgets.models import FacultyWidget, UniversityWidget from apps.sections.models import SectionMembership, SectionsConfiguration - class UserProfileForm(BaseModelForm): FIELDS_TO_CONFIGURATION = { UserProfile.nationality: SectionsConfiguration.required_nationality, @@ -22,17 +21,21 @@ class UserProfileForm(BaseModelForm): _FIELD_NAMES_TO_CONFIGURATION = {f.field.name: conf_field for f, conf_field in FIELDS_TO_CONFIGURATION.items()} @classmethod - def for_user( - cls, - user: User, - ) -> type[UserProfileForm]: - """ - Creates the profile form class for specific user. - Fields and configuration are constructed from all SectionsConfiguration from - all sections from all memberships of that specific user. - """ + def get_form_fields(cls, user: User): + confs = cls.get_user_configuration(user) + # TODO: what to do, when no specific configuration is found? + + fields_to_include = tuple( + field_name + for field_name, conf_field in cls._FIELD_NAMES_TO_CONFIGURATION.items() + if any(conf_field.__get__(c) is not None for c in confs) + ) + return cls.Meta.fields + fields_to_include + + @classmethod + def get_user_configuration(cls, user: User): # all related configurations - confs = SectionsConfiguration.objects.filter( + return SectionsConfiguration.objects.filter( # from all user's memberships sections plugins__section__memberships__in=user.memberships.filter( # with waiting for confirmation or already active membership @@ -43,23 +46,28 @@ def for_user( ) ) - # TODO: what to do, when no specific configuration is found? - + @classmethod + def for_user( + cls, + user: User, + ) -> type[UserProfileForm]: + """ + Creates the profile form class for specific user. + Fields and configuration are constructed from all SectionsConfiguration from + all sections from all memberships of that specific user. + """ def callback(f: Field, **kwargs) -> FormField: + confs = cls.get_user_configuration(user) + # TODO: what to do, when no specific configuration is found? + if conf_field := cls._FIELD_NAMES_TO_CONFIGURATION.get(f.name): return f.formfield(required=any(conf_field.__get__(c) for c in confs), **kwargs) return f.formfield(**kwargs) - fields_to_include = tuple( - field_name - for field_name, conf_field in cls._FIELD_NAMES_TO_CONFIGURATION.items() - if any(conf_field.__get__(c) is not None for c in confs) - ) - return modelform_factory( model=UserProfile, form=cls, - fields=cls.Meta.fields + fields_to_include, + fields= cls.get_form_fields(user), formfield_callback=callback, ) diff --git a/fiesta/apps/accounts/templates/accounts/dashboard_block.html b/fiesta/apps/accounts/templates/accounts/dashboard_block.html index 5abad00c..88b49349 100644 --- a/fiesta/apps/accounts/templates/accounts/dashboard_block.html +++ b/fiesta/apps/accounts/templates/accounts/dashboard_block.html @@ -4,7 +4,7 @@
{% blocktrans %}My Profile{% endblocktrans %}
- {% compute_profile_fullness user.profile as fullness %} + {% compute_profile_fullness user as fullness %} {% interpolate_to_list fullness "text-red-400" "text-orange-400" "text-blue-400" "text-lime-400" as color %}
{# TODO: compute completness #} diff --git a/fiesta/apps/accounts/templatetags/user_profile.py b/fiesta/apps/accounts/templatetags/user_profile.py index b0acd8f5..32ccabdf 100644 --- a/fiesta/apps/accounts/templatetags/user_profile.py +++ b/fiesta/apps/accounts/templatetags/user_profile.py @@ -2,6 +2,7 @@ from django import template +from apps.accounts.forms.profile import UserProfileForm from apps.accounts.models import User, UserProfile # from apps.plugins.middleware.plugin import HttpRequest @@ -18,13 +19,13 @@ def get_user_picture(user: User | None): return profile.picture -@register.simple_tag(takes_context=True) -def compute_profile_fullness(context: dict, profile: UserProfile) -> float: - fields = profile._meta.get_fields() # Get all field names of UserProfile +@register.simple_tag +def compute_profile_fullness( user: User) -> float: + fields = UserProfileForm().get_form_fields(user) # Get all field names of UserProfile empty_fields = 0 for field in fields: - field_value = getattr(profile, field.name, None) + field_value = getattr(user.profile, field, None) if field_value is None or field_value == '': empty_fields += 1 From 19d28020dec25fab04d820cf87855afa652402b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C4=8Ce=C5=A1ka?= Date: Tue, 5 Sep 2023 11:36:19 +0200 Subject: [PATCH 3/7] code style --- fiesta/apps/accounts/forms/profile.py | 8 +++++--- fiesta/apps/accounts/templatetags/user_profile.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/fiesta/apps/accounts/forms/profile.py b/fiesta/apps/accounts/forms/profile.py index 6c21bcde..2e52bde6 100644 --- a/fiesta/apps/accounts/forms/profile.py +++ b/fiesta/apps/accounts/forms/profile.py @@ -10,6 +10,7 @@ from apps.fiestaforms.widgets.models import FacultyWidget, UniversityWidget from apps.sections.models import SectionMembership, SectionsConfiguration + class UserProfileForm(BaseModelForm): FIELDS_TO_CONFIGURATION = { UserProfile.nationality: SectionsConfiguration.required_nationality, @@ -48,14 +49,15 @@ def get_user_configuration(cls, user: User): @classmethod def for_user( - cls, - user: User, + cls, + user: User, ) -> type[UserProfileForm]: """ Creates the profile form class for specific user. Fields and configuration are constructed from all SectionsConfiguration from all sections from all memberships of that specific user. """ + def callback(f: Field, **kwargs) -> FormField: confs = cls.get_user_configuration(user) # TODO: what to do, when no specific configuration is found? @@ -67,7 +69,7 @@ def callback(f: Field, **kwargs) -> FormField: return modelform_factory( model=UserProfile, form=cls, - fields= cls.get_form_fields(user), + fields=cls.get_form_fields(user), formfield_callback=callback, ) diff --git a/fiesta/apps/accounts/templatetags/user_profile.py b/fiesta/apps/accounts/templatetags/user_profile.py index 32ccabdf..8d0e672f 100644 --- a/fiesta/apps/accounts/templatetags/user_profile.py +++ b/fiesta/apps/accounts/templatetags/user_profile.py @@ -19,8 +19,9 @@ def get_user_picture(user: User | None): return profile.picture + @register.simple_tag -def compute_profile_fullness( user: User) -> float: +def compute_profile_fullness(user: User) -> float: fields = UserProfileForm().get_form_fields(user) # Get all field names of UserProfile empty_fields = 0 @@ -30,4 +31,3 @@ def compute_profile_fullness( user: User) -> float: empty_fields += 1 return (len(fields) - empty_fields) / len(fields) - From d1984dc8c8b9705b4184da49446ca494f5483ef3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 7 Sep 2023 18:14:02 +0000 Subject: [PATCH 4/7] [pre-commit.ci] auto fixes from pre-commit.com hooks --- fiesta/apps/accounts/forms/profile.py | 4 ++-- fiesta/apps/accounts/templatetags/user_profile.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fiesta/apps/accounts/forms/profile.py b/fiesta/apps/accounts/forms/profile.py index 2e52bde6..6d2a3b8f 100644 --- a/fiesta/apps/accounts/forms/profile.py +++ b/fiesta/apps/accounts/forms/profile.py @@ -49,8 +49,8 @@ def get_user_configuration(cls, user: User): @classmethod def for_user( - cls, - user: User, + cls, + user: User, ) -> type[UserProfileForm]: """ Creates the profile form class for specific user. diff --git a/fiesta/apps/accounts/templatetags/user_profile.py b/fiesta/apps/accounts/templatetags/user_profile.py index 8d0e672f..3eb64abe 100644 --- a/fiesta/apps/accounts/templatetags/user_profile.py +++ b/fiesta/apps/accounts/templatetags/user_profile.py @@ -27,7 +27,7 @@ def compute_profile_fullness(user: User) -> float: for field in fields: field_value = getattr(user.profile, field, None) - if field_value is None or field_value == '': + if field_value is None or field_value == "": empty_fields += 1 return (len(fields) - empty_fields) / len(fields) From 7434e7c16a3dff8afac42d7c3f9ee65367529c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C4=8Ce=C5=A1ka?= Date: Fri, 8 Sep 2023 13:10:30 +0200 Subject: [PATCH 5/7] code fix, comment --- fiesta/apps/accounts/templatetags/user_profile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fiesta/apps/accounts/templatetags/user_profile.py b/fiesta/apps/accounts/templatetags/user_profile.py index 3eb64abe..0b9a5913 100644 --- a/fiesta/apps/accounts/templatetags/user_profile.py +++ b/fiesta/apps/accounts/templatetags/user_profile.py @@ -22,12 +22,12 @@ def get_user_picture(user: User | None): @register.simple_tag def compute_profile_fullness(user: User) -> float: - fields = UserProfileForm().get_form_fields(user) # Get all field names of UserProfile + fields = UserProfileForm.get_form_fields(user) # Get all field names of UserProfile empty_fields = 0 for field in fields: field_value = getattr(user.profile, field, None) - if field_value is None or field_value == "": + if field_value is None or field_value == "": # So far it's not possible to have a field with False value empty_fields += 1 return (len(fields) - empty_fields) / len(fields) From 66e71640d3d23a03795e292d070431f9213cd260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C4=8Ce=C5=A1ka?= Date: Sun, 10 Sep 2023 18:15:28 +0200 Subject: [PATCH 6/7] performance enhancements --- fiesta/apps/accounts/forms/profile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fiesta/apps/accounts/forms/profile.py b/fiesta/apps/accounts/forms/profile.py index 6d2a3b8f..9bb6c484 100644 --- a/fiesta/apps/accounts/forms/profile.py +++ b/fiesta/apps/accounts/forms/profile.py @@ -49,17 +49,17 @@ def get_user_configuration(cls, user: User): @classmethod def for_user( - cls, - user: User, + cls, + user: User, ) -> type[UserProfileForm]: """ Creates the profile form class for specific user. Fields and configuration are constructed from all SectionsConfiguration from all sections from all memberships of that specific user. """ + confs = cls.get_user_configuration(user) def callback(f: Field, **kwargs) -> FormField: - confs = cls.get_user_configuration(user) # TODO: what to do, when no specific configuration is found? if conf_field := cls._FIELD_NAMES_TO_CONFIGURATION.get(f.name): From ff4f47749c33672554ac30c211987d217f9c6728 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 10 Sep 2023 17:09:05 +0000 Subject: [PATCH 7/7] [pre-commit.ci] auto fixes from pre-commit.com hooks --- fiesta/apps/accounts/forms/profile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fiesta/apps/accounts/forms/profile.py b/fiesta/apps/accounts/forms/profile.py index 9bb6c484..5f1afda3 100644 --- a/fiesta/apps/accounts/forms/profile.py +++ b/fiesta/apps/accounts/forms/profile.py @@ -49,8 +49,8 @@ def get_user_configuration(cls, user: User): @classmethod def for_user( - cls, - user: User, + cls, + user: User, ) -> type[UserProfileForm]: """ Creates the profile form class for specific user.