diff --git a/funnel/assets/js/utils/ractive_util.js b/funnel/assets/js/utils/ractive_util.js index e0bf24a85..960da4388 100644 --- a/funnel/assets/js/utils/ractive_util.js +++ b/funnel/assets/js/utils/ractive_util.js @@ -5,7 +5,7 @@ import { USER_AVATAR_IMG_SIZE } from '../constants'; Ractive.DEBUG = false; export const useravatar = Ractive.extend({ - template: `{{#if user.profile_url && addprofilelink }}{{#if user.logo_url }}{{else}}
{{ getInitials(user.fullname) }}
{{/if}}
{{else}}{{#if user.logo_url }}{{else}}
{{ getInitials(user.fullname) }}
{{/if}}
{{/if}}`, + template: `{{#if addprofilelink }}{{#if user.logo_url }}{{else}}
{{ getInitials(user.fullname) }}
{{/if}}
{{else}}{{#if user.logo_url }}{{else}}
{{ getInitials(user.fullname) }}
{{/if}}
{{/if}}`, data: { addprofilelink: true, size: 'medium', diff --git a/funnel/assets/js/utils/vue_util.js b/funnel/assets/js/utils/vue_util.js index f4078556c..aa4eb056d 100644 --- a/funnel/assets/js/utils/vue_util.js +++ b/funnel/assets/js/utils/vue_util.js @@ -5,7 +5,7 @@ import { USER_AVATAR_IMG_SIZE } from '../constants'; export const userAvatarUI = Vue.component('useravatar', { template: - '
{{ getInitials(user.fullname) }}
{{ getInitials(user.fullname) }}', + '
{{ getInitials(user.fullname) }}
{{ getInitials(user.fullname) }}', props: { user: Object, addprofilelink: { diff --git a/funnel/models/account.py b/funnel/models/account.py index b594dd2d5..a1908eb49 100644 --- a/funnel/models/account.py +++ b/funnel/models/account.py @@ -337,7 +337,7 @@ class Account(UuidMixin, BaseMixin, Model): 'logo_url', 'banner_image_url', 'joined_at', - 'profile_url', + 'absolute_url', 'urls', 'is_user_profile', 'is_organization_profile', @@ -362,7 +362,7 @@ class Account(UuidMixin, BaseMixin, Model): 'logo_url', 'website', 'joined_at', - 'profile_url', + 'absolute_url', 'is_verified', }, 'related': { @@ -378,7 +378,7 @@ class Account(UuidMixin, BaseMixin, Model): 'description', 'logo_url', 'joined_at', - 'profile_url', + 'absolute_url', 'is_verified', }, } @@ -623,13 +623,6 @@ def has_public_profile(self) -> bool: with_roles(has_public_profile, read={'all'}, write={'owner'}) - @property - def profile_url(self) -> str | None: - """Return optional URL to account profile page.""" - return self.url_for(_external=True) - - with_roles(profile_url, read={'all'}) - def is_profile_complete(self) -> bool: """Verify if profile is complete (fullname, username and contacts present).""" return bool(self.title and self.name and self.has_verified_contact_info) @@ -1297,7 +1290,7 @@ class DuckTypeAccount(RoleMixin): uuid_b58: None = None username: None = None name: None = None - profile_url: None = None + absolute_url: None = None email: None = None phone: None = None @@ -1318,7 +1311,7 @@ class DuckTypeAccount(RoleMixin): 'username', 'fullname', 'pickername', - 'profile_url', + 'absolute_url', }, 'call': {'views', 'forms', 'features', 'url_for'}, } @@ -1329,7 +1322,7 @@ class DuckTypeAccount(RoleMixin): 'username', 'fullname', 'pickername', - 'profile_url', + 'absolute_url', } } diff --git a/funnel/models/comment.py b/funnel/models/comment.py index b10eb07c9..c0361d9e6 100644 --- a/funnel/models/comment.py +++ b/funnel/models/comment.py @@ -278,16 +278,16 @@ class Comment(UuidMixin, BaseMixin, Model): __roles__ = { 'all': { - 'read': {'created_at', 'urls', 'uuid_b58', 'has_replies'}, + 'read': {'created_at', 'urls', 'uuid_b58', 'has_replies', 'absolute_url'}, 'call': {'state', 'commentset', 'view_for', 'url_for'}, }, 'replied_to_commenter': {'granted_via': {'in_reply_to': '_posted_by'}}, } __datasets__ = { - 'primary': {'created_at', 'urls', 'uuid_b58'}, - 'related': {'created_at', 'urls', 'uuid_b58'}, - 'json': {'created_at', 'urls', 'uuid_b58'}, + 'primary': {'created_at', 'urls', 'uuid_b58', 'absolute_url'}, + 'related': {'created_at', 'urls', 'uuid_b58', 'absolute_url'}, + 'json': {'created_at', 'urls', 'uuid_b58', 'absolute_url'}, 'minimal': {'created_at', 'uuid_b58'}, } @@ -361,12 +361,6 @@ def _message_expression(cls): message, read={'all'}, datasets={'primary', 'related', 'json', 'minimal'} ) - @property - def absolute_url(self) -> str: - return self.url_for() - - with_roles(absolute_url, read={'all'}, datasets={'primary', 'related', 'json'}) - @property def title(self) -> str: obj = self.commentset.parent diff --git a/funnel/models/sync_ticket.py b/funnel/models/sync_ticket.py index 29f99708d..cc717b686 100644 --- a/funnel/models/sync_ticket.py +++ b/funnel/models/sync_ticket.py @@ -297,10 +297,10 @@ def has_public_profile(self) -> bool: with_roles(has_public_profile, read={'all'}) @property - def profile_url(self) -> str | None: - return self.participant.profile_url if self.participant else None + def absolute_url(self) -> str | None: + return self.participant.absolute_url if self.participant else None - with_roles(profile_url, read={'all'}) + with_roles(absolute_url, read={'all'}) @classmethod def get( diff --git a/funnel/templates/account.html.jinja2 b/funnel/templates/account.html.jinja2 index 0d8581457..d7cf48be3 100644 --- a/funnel/templates/account.html.jinja2 +++ b/funnel/templates/account.html.jinja2 @@ -36,7 +36,7 @@ {{ faicon(icon='info-circle', icon_size='body2', baseline=true) }} {% trans %}Add username{% endtrans %} {{ faicon(icon='plus', icon_size='caption', baseline=false) }}

{%- endif %} - {%- trans %}Go to account{% endtrans %} diff --git a/funnel/templates/account_menu.html.jinja2 b/funnel/templates/account_menu.html.jinja2 index 2971a01f8..382e9e75f 100644 --- a/funnel/templates/account_menu.html.jinja2 +++ b/funnel/templates/account_menu.html.jinja2 @@ -6,25 +6,17 @@ diff --git a/funnel/templates/account_organizations.html.jinja2 b/funnel/templates/account_organizations.html.jinja2 index 65072ad0d..1a5aec647 100644 --- a/funnel/templates/account_organizations.html.jinja2 +++ b/funnel/templates/account_organizations.html.jinja2 @@ -30,7 +30,7 @@ {% for orgmem in current_auth.user.views.organizations_as_admin() %}
  • -

    {{ orgmem.account.title }} diff --git a/funnel/templates/macros.html.jinja2 b/funnel/templates/macros.html.jinja2 index 65db20a80..5d508bc80 100644 --- a/funnel/templates/macros.html.jinja2 +++ b/funnel/templates/macros.html.jinja2 @@ -279,13 +279,13 @@ {%- else %} {%- set imgsize = 48 %} {% endif %} - {% if user.profile_url and add_profile_link %}{% endif %} + {% if add_profile_link %}{% endif %} {% if user.logo_url %} {{ user.title }} {%- elif user.title %}

    {% endif %} - {% if user.profile_url and add_profile_link %}{% endif %} + {% if add_profile_link %}{% endif %} {%- endmacro %} {%- macro list_sponsors(sponsors) %} @@ -300,7 +300,7 @@ {%- endwith %} {%- endfor %} diff --git a/funnel/templates/profile_layout.html.jinja2 b/funnel/templates/profile_layout.html.jinja2 index 4df0fee3b..0a7a9a1a7 100644 --- a/funnel/templates/profile_layout.html.jinja2 +++ b/funnel/templates/profile_layout.html.jinja2 @@ -75,14 +75,14 @@
    {%- if not current_auth.is_anonymous %} diff --git a/funnel/templates/project_comments.html.jinja2 b/funnel/templates/project_comments.html.jinja2 index 138c592ac..8fb9e75eb 100644 --- a/funnel/templates/project_comments.html.jinja2 +++ b/funnel/templates/project_comments.html.jinja2 @@ -33,7 +33,7 @@ divElem: "#comments-wrapper", commentTemplate: '#comment-template', isuserloggedin: {% if current_auth.user -%}true{% else %}false{% endif %}, - user: {% if current_auth.user -%}{{ { 'fullname': current_auth.user.fullname, 'avatar': current_auth.user.logo_url, 'profile_url': current_auth.user.profile_url }|tojson }}{% else %}{}{% endif %}, + user: {% if current_auth.user -%}{{ { 'fullname': current_auth.user.fullname, 'avatar': current_auth.user.logo_url, 'absolute_url': current_auth.user.absolute_url }|tojson }}{% else %}{}{% endif %}, loginUrl: "{{ url_for('login') }}", lastSeenUrl: {% if subscribed %}{{ last_seen_url|tojson }}{% else %}false{% endif %}, }; diff --git a/funnel/templates/project_layout.html.jinja2 b/funnel/templates/project_layout.html.jinja2 index 6f5329543..a47cbb691 100644 --- a/funnel/templates/project_layout.html.jinja2 +++ b/funnel/templates/project_layout.html.jinja2 @@ -120,7 +120,7 @@
    {%- if project.account.logo_url.url %} - + {% endif %} @@ -323,7 +323,7 @@ {% if project.account.description.html %} - + {% endif %}
    @@ -363,7 +363,7 @@ {% if sponsorship.member.description.html %} -
    {{ sponsorship.member.description.html|preview(min=200, max=300) }} {% if sponsor_public %}{% trans %}more{% endtrans %}{{ faicon(icon='caret-right-solid', baseline=false, css_class="mui--align-middle") }}{% endif %}
    +
    {{ sponsorship.member.description.html|preview(min=200, max=300) }} {% if sponsor_public %}{% trans %}more{% endtrans %}{{ faicon(icon='caret-right-solid', baseline=false, css_class="mui--align-middle") }}{% endif %}
    {% endif %} {% if sponsorship.is_promoted %}

    {{ faicon(icon='angle-double-up', baseline=false, css_class="mui--text-light fa-icon--right-margin mui--align-middle") }}{% trans %}Promoted{% endtrans %}

    diff --git a/funnel/templates/submission.html.jinja2 b/funnel/templates/submission.html.jinja2 index a097184f8..130dbd662 100644 --- a/funnel/templates/submission.html.jinja2 +++ b/funnel/templates/submission.html.jinja2 @@ -439,7 +439,7 @@ divElem: "#comments-wrapper", commentTemplate: '#comment-template', isuserloggedin: {% if current_auth.user -%}true{% else %}false{% endif %}, - user: {% if current_auth.user -%}{{ { 'fullname': current_auth.user.fullname, 'avatar': current_auth.user.logo_url, 'profile_url': current_auth.user.profile_url }|tojson }}{% else %}{}{% endif %}, + user: {% if current_auth.user -%}{{ { 'fullname': current_auth.user.fullname, 'avatar': current_auth.user.logo_url, 'absolute_url': current_auth.user.absolute_url }|tojson }}{% else %}{}{% endif %}, loginUrl: "{{ url_for('login') }}", lastSeenUrl: {% if proposal.commentset.current_roles.document_subscriber %}{{ proposal.commentset.url_for('update_last_seen_at')|tojson }}{% else %}false{% endif %}, }; diff --git a/funnel/views/api/support.py b/funnel/views/api/support.py index 37f909c29..d495ae2af 100644 --- a/funnel/views/api/support.py +++ b/funnel/views/api/support.py @@ -61,8 +61,9 @@ def support_callerid(number: str) -> tuple[dict[str, Any], int]: if phone_number.used_in_account_phone: user_phone = phone_number.used_in_account_phone[0] info['account'] = { - 'title': user_phone.account.fullname, - 'name': user_phone.account.username, + 'title': user_phone.account.title, + 'name': user_phone.account.name, + 'url': user_phone.account.absolute_url, } return {'status': 'ok', 'result': info}, 200 # TODO: Check in TicketParticipant.phone diff --git a/funnel/views/notifications/comment_notification.py b/funnel/views/notifications/comment_notification.py index 09a49e350..574066536 100644 --- a/funnel/views/notifications/comment_notification.py +++ b/funnel/views/notifications/comment_notification.py @@ -208,13 +208,9 @@ def activity_html(self, comment: Comment | None = None) -> str: if comment is None: comment = self.comment - actor_markup = ( - Markup( - f'' - f'{escape(self.actor.pickername)}' - ) - if self.actor.profile_url - else escape(self.actor.pickername) + actor_markup = Markup( + f'' + f'{escape(self.actor.pickername)}' ) project = self.project project_markup = ( diff --git a/funnel/views/notifications/organization_membership_notification.py b/funnel/views/notifications/organization_membership_notification.py index eb4b2509f..97faf526e 100644 --- a/funnel/views/notifications/organization_membership_notification.py +++ b/funnel/views/notifications/organization_membership_notification.py @@ -391,22 +391,18 @@ def activity_html(self, membership: AccountMembership | None = None) -> str: actor = self.membership_actor(membership) return Markup(self.activity_template(membership)).format( user=Markup( - f'' + f'' f'{escape(membership.member.pickername)}' - ) - if membership.member.profile_url - else escape(membership.member.pickername), + ), organization=Markup( - f'' + f'' f'{escape(self.organization.pickername)}' ), actor=( Markup( - f'' + f'' f'{escape(actor.pickername)}' ) - if actor.profile_url - else escape(actor.pickername) ) if actor else _("(unknown)"), diff --git a/funnel/views/notifications/project_crew_notification.py b/funnel/views/notifications/project_crew_notification.py index 69515dba9..e6e3d1227 100644 --- a/funnel/views/notifications/project_crew_notification.py +++ b/funnel/views/notifications/project_crew_notification.py @@ -720,22 +720,18 @@ def activity_html(self, membership: ProjectMembership | None = None) -> str: actor = self.membership_actor(membership) return Markup(self.activity_template(membership)).format( user=Markup( - f'' + f'' f'{escape(membership.member.pickername)}' - ) - if membership.member.profile_url - else escape(membership.member.pickername), + ), project=Markup( f'' f'{escape(self.project.joined_title)}' ), actor=( Markup( - f'' + f'' f'{escape(actor.pickername)}' ) - if actor.profile_url - else escape(actor.pickername) ) if actor else _("(unknown)"), diff --git a/funnel/views/project.py b/funnel/views/project.py index 97ba47087..272176278 100644 --- a/funnel/views/project.py +++ b/funnel/views/project.py @@ -497,7 +497,7 @@ def delete(self) -> ReturnView: success=_( "You have deleted project ‘{title}’ and all its associated content" ).format(title=self.obj.title), - next=self.obj.account.profile_url, + next=self.obj.account.absolute_url, cancel_url=self.obj.url_for(), ) diff --git a/funnel/views/proposal.py b/funnel/views/proposal.py index 6e6dc40fd..b4fd88ae4 100644 --- a/funnel/views/proposal.py +++ b/funnel/views/proposal.py @@ -426,7 +426,7 @@ def contacts_json(self): { 'fullname': membership.member.fullname, 'username': membership.member.username, - 'profile': membership.member.profile_url, + 'profile': membership.member.absolute_url, 'email': str(membership.member.email), 'phone': str(membership.member.phone), } diff --git a/tests/unit/views/account_test.py b/tests/unit/views/account_test.py index f4ac03dd1..bc7068101 100644 --- a/tests/unit/views/account_test.py +++ b/tests/unit/views/account_test.py @@ -7,6 +7,14 @@ from funnel.views.account import user_agent_details +def test_account_always_has_profile_url(user_twoflower, user_rincewind) -> None: + """An account without a username will still have an absolute URL for a profile.""" + assert user_twoflower.username is None + assert user_twoflower.absolute_url is not None + assert user_rincewind.username is not None + assert user_rincewind.absolute_url is not None + + def test_username_available(db_session, client, user_rincewind, csrf_token) -> None: """Test the username availability endpoint.""" endpoint = '/api/1/account/username_available' diff --git a/tests/unit/views/api_support_test.py b/tests/unit/views/api_support_test.py index c92431e6c..1ab022b31 100644 --- a/tests/unit/views/api_support_test.py +++ b/tests/unit/views/api_support_test.py @@ -104,6 +104,7 @@ def test_valid_phone_affiliated( assert data['result']['account'] == { 'title': user_rincewind_phone.account.fullname, 'name': user_rincewind_phone.account.username, + 'url': user_rincewind_phone.account.absolute_url, } @@ -129,4 +130,5 @@ def test_valid_phone_intl( assert data['result']['account'] == { 'title': user_twoflower_phone.account.fullname, 'name': user_twoflower_phone.account.username, + 'url': user_twoflower_phone.account.absolute_url, }