From c9d51ff9fc1da46aee26906cfdd65d319f03d644 Mon Sep 17 00:00:00 2001
From: Saurabh Kumar <theskumar@users.noreply.github.com>
Date: Sat, 15 Jun 2024 11:49:23 +0530
Subject: [PATCH] Add application submission id everywhere, with an optional
 prefix (#3930)

Fixes #3830

This PR allow adding an option prefix to the submission ID's that are
auto generated.

The prefix can be added by going to round or lab settings page.

This PR also ensure the prefix or application ID displayed along with
title almost everywhere for each reference. Since the title of
application is not unique, this helps add uniqueness to it.

The autolinking of submission ids in communication has be updated to
account for application id prefix
---
 .../apply/activity/adapters/activity_feed.py  | 16 ++++--
 .../activity/adapters/django_messages.py      |  8 ++-
 hypha/apply/activity/adapters/emails.py       | 12 ++--
 hypha/apply/activity/adapters/slack.py        | 55 +++++++++++--------
 .../messages/email/batch_ready_to_review.html |  1 +
 .../email/partners_update_applicant.html      |  1 +
 .../email/partners_update_partner.html        |  1 +
 .../messages/email/ready_to_review.html       |  2 +-
 .../email/submission_confirmation.html        |  3 +-
 .../dashboard/community_dashboard.html        |  3 +-
 .../partials/applicant_submissions.html       |  7 ++-
 .../dashboard/partner_dashboard.html          |  2 +-
 hypha/apply/determinations/models.py          |  4 +-
 .../base_determination_form.html              |  2 +-
 .../batch_determination_form.html             |  2 +-
 .../determinations/determination_detail.html  |  2 +-
 .../determinations/determination_form.html    |  2 +-
 hypha/apply/determinations/views.py           |  4 +-
 .../commands/export_submissions_csv.py        |  2 +-
 ...pplicationsubmission_public_id_and_more.py | 44 +++++++++++++++
 .../funds/models/application_revisions.py     |  2 +-
 hypha/apply/funds/models/applications.py      | 23 ++++++++
 hypha/apply/funds/models/submissions.py       | 33 +++++++++--
 hypha/apply/funds/tables.py                   |  8 +--
 .../funds/applicationrevision_list.html       |  2 +-
 .../applicationsubmission_confirm_delete.html |  6 +-
 .../funds/applicationsubmission_detail.html   | 14 ++++-
 .../funds/applicationsubmission_form.html     |  4 +-
 .../funds/includes/rendered_answers.html      |  4 +-
 .../funds/includes/submission-list-item.html  |  2 +-
 .../templates/funds/revisions_compare.html    |  2 +-
 .../funds/templatetags/submission_tags.py     | 15 +++--
 hypha/apply/funds/tests/test_tags.py          | 10 ++--
 .../review/templates/review/review_list.html  |  2 +-
 hypha/settings/base.py                        |  6 ++
 hypha/static_src/javascript/batch-actions.js  |  2 +-
 hypha/static_src/sass/base/_base.scss         |  4 --
 37 files changed, 224 insertions(+), 88 deletions(-)
 create mode 100644 hypha/apply/funds/migrations/0120_applicationsubmission_public_id_and_more.py

diff --git a/hypha/apply/activity/adapters/activity_feed.py b/hypha/apply/activity/adapters/activity_feed.py
index 6131f0ffe..97fd44220 100644
--- a/hypha/apply/activity/adapters/activity_feed.py
+++ b/hypha/apply/activity/adapters/activity_feed.py
@@ -23,7 +23,9 @@ class ActivityAdapter(AdapterBase):
     messages = {
         MESSAGES.TRANSITION: "handle_transition",
         MESSAGES.BATCH_TRANSITION: "handle_batch_transition",
-        MESSAGES.NEW_SUBMISSION: _("Submitted {source.title} for {source.page.title}"),
+        MESSAGES.NEW_SUBMISSION: _(
+            "Submitted {source.title_text_display} for {source.page.title}"
+        ),
         MESSAGES.EDIT_SUBMISSION: _("Edited"),
         MESSAGES.APPLICANT_EDIT: _("Edited"),
         MESSAGES.UPDATE_LEAD: _("Lead changed from {old_lead} to {source.lead}"),
@@ -69,10 +71,10 @@ class ActivityAdapter(AdapterBase):
         MESSAGES.BATCH_ARCHIVE_SUBMISSION: "handle_batch_archive_submission",
         MESSAGES.BATCH_UPDATE_INVOICE_STATUS: "handle_batch_update_invoice_status",
         MESSAGES.ARCHIVE_SUBMISSION: _(
-            "{user} has archived the submission: {source.title}"
+            "{user} has archived the submission: {source.title_text_display}"
         ),
         MESSAGES.UNARCHIVE_SUBMISSION: _(
-            "{user} has unarchived the submission: {source.title}"
+            "{user} has unarchived the submission: {source.title_text_display}"
         ),
         MESSAGES.DELETE_INVOICE: _("Deleted an invoice"),
     }
@@ -154,14 +156,18 @@ class ActivityAdapter(AdapterBase):
 
     def handle_batch_delete_submission(self, sources, **kwargs):
         submissions = sources
-        submissions_text = ", ".join([submission.title for submission in submissions])
+        submissions_text = ", ".join(
+            [submission.title_text_display for submission in submissions]
+        )
         return _("Successfully deleted submissions: {title}").format(
             title=submissions_text
         )
 
     def handle_batch_archive_submission(self, sources, **kwargs):
         submissions = sources
-        submissions_text = ", ".join([submission.title for submission in submissions])
+        submissions_text = ", ".join(
+            [submission.title_text_display for submission in submissions]
+        )
         return _("Successfully archived submissions: {title}").format(
             title=submissions_text
         )
diff --git a/hypha/apply/activity/adapters/django_messages.py b/hypha/apply/activity/adapters/django_messages.py
index 45f5f89b9..5fdcb0a6c 100644
--- a/hypha/apply/activity/adapters/django_messages.py
+++ b/hypha/apply/activity/adapters/django_messages.py
@@ -33,7 +33,9 @@ class DjangoMessagesAdapter(AdapterBase):
 
         return _("Batch reviewers added: {reviewers_text} to ").format(
             reviewers_text=reviewers_text
-        ) + ", ".join(['"{title}"'.format(title=source.title) for source in sources])
+        ) + ", ".join(
+            ['"{title}"'.format(title=source.title_text_display) for source in sources]
+        )
 
     def handle_report_frequency(self, config, **kwargs):
         new_schedule = config.get_frequency_display()
@@ -56,7 +58,7 @@ class DjangoMessagesAdapter(AdapterBase):
         transition = "{submission} [{old_display} → {new_display}]."
         transition_messages = [
             transition.format(
-                submission=submission.title,
+                submission=submission.title_text_display,
                 old_display=transitions[submission.id],
                 new_display=submission.phase,
             )
@@ -72,7 +74,7 @@ class DjangoMessagesAdapter(AdapterBase):
         base_message = _("Successfully determined as {outcome}: ").format(
             outcome=outcome
         )
-        submissions_text = [str(submission.title) for submission in submissions]
+        submissions_text = [submission.title_text_display for submission in submissions]
         return base_message + ", ".join(submissions_text)
 
     def recipients(self, *args, **kwargs):
diff --git a/hypha/apply/activity/adapters/emails.py b/hypha/apply/activity/adapters/emails.py
index 6ef0c127a..0cd1210a2 100644
--- a/hypha/apply/activity/adapters/emails.py
+++ b/hypha/apply/activity/adapters/emails.py
@@ -77,9 +77,9 @@ class EmailAdapter(AdapterBase):
     def get_subject(self, message_type, source):
         if source and hasattr(source, "title"):
             if is_ready_for_review(message_type) or is_reviewer_update(message_type):
-                subject = _("Application ready to review: {source.title}").format(
-                    source=source
-                )
+                subject = _(
+                    "Application ready to review: {source.title_text_display}"
+                ).format(source=source)
                 if message_type in {
                     MESSAGES.BATCH_READY_FOR_REVIEW,
                     MESSAGES.BATCH_REVIEWERS_UPDATED,
@@ -87,7 +87,7 @@ class EmailAdapter(AdapterBase):
                     subject = _("Multiple applications are now ready for your review")
             elif message_type in {MESSAGES.REVIEW_REMINDER}:
                 subject = _(
-                    "Reminder: Application ready to review: {source.title}"
+                    "Reminder: Application ready to review: {source.title_text_display}"
                 ).format(source=source)
             elif message_type in [
                 MESSAGES.SENT_TO_COMPLIANCE,
@@ -136,7 +136,7 @@ class EmailAdapter(AdapterBase):
             else:
                 try:
                     subject = source.page.specific.subject or _(
-                        "Your application to {org_long_name}: {source.title}"
+                        "Your application to {org_long_name}: {source.title_text_display}"
                     ).format(org_long_name=settings.ORG_LONG_NAME, source=source)
                 except AttributeError:
                     subject = _("Your {org_long_name} Project: {source.title}").format(
@@ -153,7 +153,7 @@ class EmailAdapter(AdapterBase):
         from hypha.apply.funds.workflow import PHASES
 
         submission = source
-        # Retrive status index to see if we are going forward or backward.
+        # Retrieve status index to see if we are going forward or backward.
         old_index = list(dict(PHASES).keys()).index(old_phase.name)
         target_index = list(dict(PHASES).keys()).index(submission.status)
         is_forward = old_index < target_index
diff --git a/hypha/apply/activity/adapters/slack.py b/hypha/apply/activity/adapters/slack.py
index 2f5b7446e..9e2351688 100644
--- a/hypha/apply/activity/adapters/slack.py
+++ b/hypha/apply/activity/adapters/slack.py
@@ -31,51 +31,55 @@ class SlackAdapter(AdapterBase):
     always_send = True
     messages = {
         MESSAGES.NEW_SUBMISSION: _(
-            "A new submission has been submitted for {source.page.title}: <{link}|{source.title}> by {user}"
+            "A new submission has been submitted for {source.page.title}: <{link}|{source.title_text_display}> by {user}"
         ),
         MESSAGES.UPDATE_LEAD: _(
-            "The lead of <{link}|{source.title}> has been updated from {old_lead} to {source.lead} by {user}"
+            "The lead of <{link}|{source.title_text_display}> has been updated from {old_lead} to {source.lead} by {user}"
         ),
         MESSAGES.BATCH_UPDATE_LEAD: "handle_batch_lead",
         MESSAGES.COMMENT: _(
             "A new {comment.visibility} comment has been posted on <{link}|{source.title}> by {user}"
         ),
-        MESSAGES.EDIT_SUBMISSION: _("{user} has edited <{link}|{source.title}>"),
-        MESSAGES.APPLICANT_EDIT: _("{user} has edited <{link}|{source.title}>"),
+        MESSAGES.EDIT_SUBMISSION: _(
+            "{user} has edited <{link}|{source.title_text_display}>"
+        ),
+        MESSAGES.APPLICANT_EDIT: _(
+            "{user} has edited <{link}|{source.title_text_display}>"
+        ),
         MESSAGES.REVIEWERS_UPDATED: "reviewers_updated",
         MESSAGES.BATCH_REVIEWERS_UPDATED: "handle_batch_reviewers",
         MESSAGES.PARTNERS_UPDATED: _(
-            "{user} has updated the partners on <{link}|{source.title}>"
+            "{user} has updated the partners on <{link}|{source.title_text_display}>"
         ),
         MESSAGES.TRANSITION: _(
-            "{user} has updated the status of <{link}|{source.title}>: {old_phase.display_name} → {source.phase}"
+            "{user} has updated the status of <{link}|{source.title_text_display}>: {old_phase.display_name} → {source.phase}"
         ),
         MESSAGES.BATCH_TRANSITION: "handle_batch_transition",
         MESSAGES.DETERMINATION_OUTCOME: "handle_determination",
         MESSAGES.BATCH_DETERMINATION_OUTCOME: "handle_batch_determination",
         MESSAGES.PROPOSAL_SUBMITTED: _(
-            "A proposal has been submitted for review: <{link}|{source.title}>"
+            "A proposal has been submitted for review: <{link}|{source.title_text_display}>"
         ),
         MESSAGES.INVITED_TO_PROPOSAL: _(
-            "<{link}|{source.title}> by {source.user} has been invited to submit a proposal"
+            "<{link}|{source.title_text_display}> by {source.user} has been invited to submit a proposal"
         ),
         MESSAGES.NEW_REVIEW: _(
-            "{user} has submitted a review for <{link}|{source.title}>. Outcome: {review.outcome},  Score: {review.get_score_display}"
+            "{user} has submitted a review for <{link}|{source.title_text_display}>. Outcome: {review.outcome},  Score: {review.get_score_display}"
         ),
         MESSAGES.READY_FOR_REVIEW: "notify_reviewers",
         MESSAGES.OPENED_SEALED: _(
-            "{user} has opened the sealed submission: <{link}|{source.title}>"
+            "{user} has opened the sealed submission: <{link}|{source.title_text_display}>"
         ),
         MESSAGES.REVIEW_OPINION: _(
-            "{user} {opinion.opinion_display}s with {opinion.review.author}s review of <{link}|{source.title}>"
+            "{user} {opinion.opinion_display}s with {opinion.review.author}s review of <{link}|{source.title_text_display}>"
         ),
         MESSAGES.BATCH_READY_FOR_REVIEW: "batch_notify_reviewers",
-        MESSAGES.DELETE_SUBMISSION: _("{user} has deleted {source.title}"),
+        MESSAGES.DELETE_SUBMISSION: _("{user} has deleted {source.title_text_display}"),
         MESSAGES.DELETE_REVIEW: _(
-            "{user} has deleted {review.author} review for <{link}|{source.title}>"
+            "{user} has deleted {review.author} review for <{link}|{source.title_text_display}>"
         ),
         MESSAGES.DELETE_REVIEW_OPINION: _(
-            "{user} has deleted {review_opinion.author} review opinion for <{link}|{source.title}>"
+            "{user} has deleted {review_opinion.author} review opinion for <{link}|{source.title_text_display}>"
         ),
         MESSAGES.CREATED_PROJECT: _(
             "{user} has created a Project: <{link}|{source.title}>"
@@ -87,7 +91,7 @@ class SlackAdapter(AdapterBase):
             "The project title has been updated from <{link}|{old_title}> to <{link}|{source.title}> by {user}"
         ),
         MESSAGES.EDIT_REVIEW: _(
-            "{user} has edited {review.author} review for <{link}|{source.title}>"
+            "{user} has edited {review.author} review for <{link}|{source.title_text_display}>"
         ),
         MESSAGES.SEND_FOR_APPROVAL: _(
             "{user} has requested approval on project <{link}|{source.title}>"
@@ -131,10 +135,10 @@ class SlackAdapter(AdapterBase):
         ),
         MESSAGES.BATCH_ARCHIVE_SUBMISSION: "handle_batch_archive_submission",
         MESSAGES.ARCHIVE_SUBMISSION: _(
-            "{user} has archived the submission: {source.title}"
+            "{user} has archived the submission: {source.title_text_display}"
         ),
         MESSAGES.UNARCHIVE_SUBMISSION: _(
-            "{user} has unarchived the submission: {source.title}"
+            "{user} has unarchived the submission: {source.title_text_display}"
         ),
     }
 
@@ -146,7 +150,10 @@ class SlackAdapter(AdapterBase):
         self.comments_type = settings.SLACK_TYPE_COMMENTS
 
     def slack_links(self, links, sources):
-        return ", ".join(f"<{links[source.id]}|{source.title}>" for source in sources)
+        return ", ".join(
+            f"<{links[source.id]}|{getattr(source, 'title_text_display', source.title)}>"
+            for source in sources
+        )
 
     def extra_kwargs(self, message_type, **kwargs):
         source = kwargs["source"]
@@ -236,7 +243,7 @@ class SlackAdapter(AdapterBase):
         submission = source
         message = [
             _("{user} has updated the reviewers on <{link}|{title}>").format(
-                user=user, link=link, title=submission.title
+                user=user, link=link, title=submission.title_text_display
             )
         ]
 
@@ -305,14 +312,14 @@ class SlackAdapter(AdapterBase):
                 "A determination for <{link}|{submission_title}> was sent by email. Outcome: {determination_outcome}"
             ).format(
                 link=link,
-                submission_title=submission.title,
+                submission_title=submission.title_text_display,
                 determination_outcome=determination.clean_outcome,
             )
         return _(
             "A determination for <{link}|{submission_title}> was saved without sending an email. Outcome: {determination_outcome}"
         ).format(
             link=link,
-            submission_title=submission.title,
+            submission_title=submission.title_text_display,
             determination_outcome=determination.clean_outcome,
         )
 
@@ -333,7 +340,9 @@ class SlackAdapter(AdapterBase):
 
     def handle_batch_delete_submission(self, sources, links, user, **kwargs):
         submissions = sources
-        submissions_text = ", ".join([submission.title for submission in submissions])
+        submissions_text = ", ".join(
+            [submission.title_text_display for submission in submissions]
+        )
         return _("{user} has deleted submissions: {title}").format(
             user=user, title=submissions_text
         )
@@ -359,7 +368,7 @@ class SlackAdapter(AdapterBase):
         ).format(
             link=link,
             reviewers=reviewers,
-            title=submission.title,
+            title=submission.title_text_display,
         )
 
     def batch_notify_reviewers(self, sources, links, **kwargs):
diff --git a/hypha/apply/activity/templates/messages/email/batch_ready_to_review.html b/hypha/apply/activity/templates/messages/email/batch_ready_to_review.html
index 95a18d409..d6a2b986e 100644
--- a/hypha/apply/activity/templates/messages/email/batch_ready_to_review.html
+++ b/hypha/apply/activity/templates/messages/email/batch_ready_to_review.html
@@ -6,6 +6,7 @@
 {% block content %}{# fmt:off #}
 {% trans "New applications have been added to your review list." %}
 {% for submission in sources %}
+{% trans "ID" %}: {{ submission.public_id|default:submission.id }}
 {% trans "Title" %}: {{ submission.title }}
 {% trans "Link" %}: {{ request.scheme }}://{{ request.get_host }}{{ submission.get_absolute_url }}
 {% endfor %}
diff --git a/hypha/apply/activity/templates/messages/email/partners_update_applicant.html b/hypha/apply/activity/templates/messages/email/partners_update_applicant.html
index f507ebea7..6a091eb6c 100644
--- a/hypha/apply/activity/templates/messages/email/partners_update_applicant.html
+++ b/hypha/apply/activity/templates/messages/email/partners_update_applicant.html
@@ -6,6 +6,7 @@
 {% for partner in added %}
     * {{ partner }}
 {% endfor %}
+{% trans "ID" %}: {{ submission.public_id|default:submission.id }}
 {% trans "Title" %}: {{ submission.title }}
 {% trans "Link" %}: {{ request.scheme }}://{{ request.get_host }}{{ submission.get_absolute_url }}
 {% endblock %}{# fmt:on #}
diff --git a/hypha/apply/activity/templates/messages/email/partners_update_partner.html b/hypha/apply/activity/templates/messages/email/partners_update_partner.html
index 13aa168db..36f6eac3e 100644
--- a/hypha/apply/activity/templates/messages/email/partners_update_partner.html
+++ b/hypha/apply/activity/templates/messages/email/partners_update_partner.html
@@ -6,6 +6,7 @@
 {% block content %}{# fmt:off #}
 {% trans "You have been added as a partner the following submission." %}
 
+{% trans "ID" %}: {{ submission.public_id|default:submission.id }}
 {% trans "Title" %}: {{ submission.title }}
 {% trans "Link" %}: {{ request.scheme }}://{{ request.get_host }}{{ submission.get_absolute_url }}
 {% endblock %}{# fmt:on #}
diff --git a/hypha/apply/activity/templates/messages/email/ready_to_review.html b/hypha/apply/activity/templates/messages/email/ready_to_review.html
index 622be49fe..5495ce910 100644
--- a/hypha/apply/activity/templates/messages/email/ready_to_review.html
+++ b/hypha/apply/activity/templates/messages/email/ready_to_review.html
@@ -5,7 +5,7 @@
 {% block content %}{# fmt:off #}
 {% trans "This application is awaiting your review." %}
 
-{% trans "Title" %}: {{ source.title }}
+{% trans "Title" %}: {{ source.title_text_display }}
 {% if related.title %}{% trans "Reminder Title" %}: {{ related.title }}{% endif %}
 {% if related.description %}{% trans "Reminder Description" %}: {{ related.description }}{% endif %}
 {% trans "Link" %}: {{ request.scheme }}://{{ request.get_host }}{{ source.get_absolute_url }}
diff --git a/hypha/apply/activity/templates/messages/email/submission_confirmation.html b/hypha/apply/activity/templates/messages/email/submission_confirmation.html
index cc5f2b0cd..34f1ed07a 100644
--- a/hypha/apply/activity/templates/messages/email/submission_confirmation.html
+++ b/hypha/apply/activity/templates/messages/email/submission_confirmation.html
@@ -3,7 +3,7 @@
 {% load i18n %}
 
 {% block content %}{# fmt:off #}
-{% blocktrans with title=source.title %}We appreciate your {{ title }} application submission to the {{ ORG_LONG_NAME }}.{% endblocktrans %}
+{% blocktrans with title=source.title_text_display %}We appreciate your {{ title }} application submission to the {{ ORG_LONG_NAME }}.{% endblocktrans %}
 
 {% if source.is_draft %}{% trans "Please note that it is not submitted for review because it's still in draft." %} {% trans "You can access the draft at" %}: {{ request.scheme }}://{{ request.get_host }}{{ source.get_absolute_url }}{% else %}{% trans "We will review and reply to your submission as quickly as possible." %}{% endif %}
 
@@ -15,6 +15,7 @@
 
 {% with email_context=source.page.specific %}{{ email_context.confirmation_text_extra }}{% endwith %}
 
+{% trans "Project Id" %}: {{ source.public_id|default:source.id }}
 {% trans "Project name" %}: {{ source.title }}
 {% trans "Contact name" %}: {{ source.user.get_full_name }}
 {% trans "Contact email" %}: {{ source.user.email }}
diff --git a/hypha/apply/dashboard/templates/dashboard/community_dashboard.html b/hypha/apply/dashboard/templates/dashboard/community_dashboard.html
index 2cb55b2bf..914d16c40 100644
--- a/hypha/apply/dashboard/templates/dashboard/community_dashboard.html
+++ b/hypha/apply/dashboard/templates/dashboard/community_dashboard.html
@@ -50,8 +50,9 @@
                         <div class="flex max-w-sm sm:max-w-lg flex-col gap-2 md:flex-row md:justify-between md:w-full md:max-w-none lg:flex-col lg:justify-start lg:w-auto lg:max-w-sm">
                             <div>
                                 <h3 class="heading heading--no-margin text-base font-bold">
-                                    <a class="link link--underlined" href="{% url 'funds:submissions:detail' submission.id %}">
+                                    <a class="{% if not submission.is_active %} text-slate-500 {% endif %} hover:underline" href="{% url 'funds:submissions:detail' submission.id %}">
                                         {{ submission.title }}
+                                        <span class="text-gray-400">#{{ submission.public_id|default:submission.id }}</span>
                                     </a>
                                 </h3>
                                 <p class="heading heading--no-margin text-fg-muted text-sm">
diff --git a/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html b/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html
index c5b9f454d..730d4d0db 100644
--- a/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html
+++ b/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html
@@ -4,7 +4,12 @@
     <div class="wrapper wrapper--status-bar-outer">
         <div class="wrapper wrapper--status-bar-inner ms-4">
             <div class="mt-5 lg:max-w-[30%]">
-                <h4 class="heading mb-0 font-bold line-clamp-3 hover:line-clamp-none"><a class="link {% if not submission.is_active %} text-gray-400 {% endif %}" href="{% url 'funds:submissions:detail' submission.id %}">{{ submission.title }}</a></h4>
+                <h4 class="heading mb-0 font-bold line-clamp-3 hover:line-clamp-none">
+                    <a class="{% if not submission.is_active %} text-slate-500 {% endif %} hover:underline" href="{% url 'funds:submissions:detail' submission.id %}">
+                        {{ submission.title }}
+                        <span class="text-gray-400">#{{ submission.public_id|default:submission.id }}</span>
+                    </a>
+                </h4>
                 <p class="m-0 text-fg-muted mb-4 text-sm">
                     {% if submission.is_draft %}
                         {% trans "Drafted on " %}
diff --git a/hypha/apply/dashboard/templates/dashboard/partner_dashboard.html b/hypha/apply/dashboard/templates/dashboard/partner_dashboard.html
index 3e2f13941..d77ceeda1 100644
--- a/hypha/apply/dashboard/templates/dashboard/partner_dashboard.html
+++ b/hypha/apply/dashboard/templates/dashboard/partner_dashboard.html
@@ -33,7 +33,7 @@
                 <div class="wrapper wrapper--status-bar-outer">
                     <div class="wrapper wrapper--status-bar-inner">
                         <div>
-                            <h5 class="heading heading--no-margin"><a class="link link--underlined" href="{% url 'funds:submissions:detail' submission.id %}">{{ submission.title }}</a></h5>
+                            <h5 class="heading heading--no-margin"><a class="link link--underlined" href="{% url 'funds:submissions:detail' submission.id %}">{{ submission.title_text_display }}</a></h5>
                             <h6 class="heading heading--no-margin heading--submission-meta"><span>{% trans "Submitted" %}:</span> {{ submission.submit_time.date }} {% trans "by" %} {{ submission.user.get_full_name }}</h6>
                         </div>
                         {% status_bar submission.workflow submission.phase request.user css_class="status-bar--small" %}
diff --git a/hypha/apply/determinations/models.py b/hypha/apply/determinations/models.py
index 09bbfb22c..1a595559f 100644
--- a/hypha/apply/determinations/models.py
+++ b/hypha/apply/determinations/models.py
@@ -147,7 +147,9 @@ class Determination(DeterminationFormFieldsMixin, AccessFormData, models.Model):
         return not self.is_draft
 
     def __str__(self):
-        return f"Determination for {self.submission.title} by {self.author!s}"
+        return (
+            f"Determination for {self.submission.title_text_display} by {self.author!s}"
+        )
 
     def __repr__(self):
         return f"<{self.__class__.__name__}: {str(self.form_data)}>"
diff --git a/hypha/apply/determinations/templates/determinations/base_determination_form.html b/hypha/apply/determinations/templates/determinations/base_determination_form.html
index 153c84bc6..62723bd48 100644
--- a/hypha/apply/determinations/templates/determinations/base_determination_form.html
+++ b/hypha/apply/determinations/templates/determinations/base_determination_form.html
@@ -5,7 +5,7 @@
 
     {% adminbar %}
         {% slot header %}{% if object %}{% trans "Update Determination draft" %}{% else %}{% trans "Create Determination" %}{% endif %}{% endslot %}
-        {% slot sub_heading %}{% if submission %}{% trans "For" %} <a href="{% url "funds:submissions:detail" submission.id %}">{{ submission.title }}</a>{% endif %}{% endslot %}
+        {% slot sub_heading %}{% if submission %}{% trans "For" %} <a class="text-blue-300 hover:underline" href="{% url "funds:submissions:detail" submission.id %}">{{ submission.title_text_display }}</a>{% endif %}{% endslot %}
     {% endadminbar %}
 
     {% block form %}
diff --git a/hypha/apply/determinations/templates/determinations/batch_determination_form.html b/hypha/apply/determinations/templates/determinations/batch_determination_form.html
index f6c5590b0..b4f26145c 100644
--- a/hypha/apply/determinations/templates/determinations/batch_determination_form.html
+++ b/hypha/apply/determinations/templates/determinations/batch_determination_form.html
@@ -19,7 +19,7 @@
         <div class="list-reveal__list list-reveal__list--determination js-batch-titles is-closed" aria-live="polite">
             {% for submission in submissions %}
                 <a href="{% url "funds:submissions:detail" submission.id %}" class="list-reveal__item" target="_blank" rel="noopener noreferrer" title="{{ submission.title }}">
-                    {{ submission.title }}
+                    {{ submission.title_text_display }}
                     {% heroicon_micro "arrow-top-right-on-square" class="inline align-text-bottom w-4 h-4" aria_hidden=true %}
                 </a>
             {% endfor %}
diff --git a/hypha/apply/determinations/templates/determinations/determination_detail.html b/hypha/apply/determinations/templates/determinations/determination_detail.html
index 0ca28fead..e393f8968 100644
--- a/hypha/apply/determinations/templates/determinations/determination_detail.html
+++ b/hypha/apply/determinations/templates/determinations/determination_detail.html
@@ -12,7 +12,7 @@
         {% endslot %}
 
         {% slot header %} {% trans "Determination" %} {% if determination.is_draft %}[{% trans "DRAFT" %}] {% endif %}{% endslot %}
-        {% slot sub_heading %}{% trans "For" %} <a class="text-blue-300 hover:underline" href="{% url "funds:submissions:detail" determination.submission.id %}">{{ determination.submission.title }}</a>{% endslot %}
+        {% slot sub_heading %}{% trans "For" %} <a class="text-blue-300 hover:underline" href="{% url "funds:submissions:detail" determination.submission.id %}">{{ determination.submission.title_text_display }}</a>{% endslot %}
 
     {% endadminbar %}
 
diff --git a/hypha/apply/determinations/templates/determinations/determination_form.html b/hypha/apply/determinations/templates/determinations/determination_form.html
index 0b4f365d5..0d466cafe 100644
--- a/hypha/apply/determinations/templates/determinations/determination_form.html
+++ b/hypha/apply/determinations/templates/determinations/determination_form.html
@@ -5,5 +5,5 @@
     {% slot header %}
         {% if object %}{% trans "Edit determination" %} {% if object.is_draft %}{% trans "draft" %}{% endif %}{% else %}{% trans "Create Determination" %}{% endif %}
     {% endslot %}
-    {% slot sub_heading %}{% trans "For" %} <a class="text-blue-300 hover:underline" href="{% url "funds:submissions:detail" submission.id %}">{{ submission.title }}</a>{% endslot %}
+    {% slot sub_heading %}{% trans "For" %} <a class="text-blue-300 hover:underline" href="{% url "funds:submissions:detail" submission.id %}">{{ submission.title_text_display }}</a>{% endslot %}
 {% endadminbar %}
diff --git a/hypha/apply/determinations/views.py b/hypha/apply/determinations/views.py
index 1d5b3ac32..887d775d2 100644
--- a/hypha/apply/determinations/views.py
+++ b/hypha/apply/determinations/views.py
@@ -217,7 +217,7 @@ class BatchDeterminationCreateView(BaseStreamForm, CreateView):
                 messages.warning(
                     self.request,
                     'Unable to determine submission "{title}" as already determined'.format(
-                        title=submission.title
+                        title=submission.title_text_display
                     ),
                 )
             else:
@@ -262,7 +262,7 @@ class BatchDeterminationCreateView(BaseStreamForm, CreateView):
                         "A determination already exists for the following submissions and they have been excluded: {submissions}"
                     ).format(
                         submissions=", ".join(
-                            [submission.title for submission in excluded]
+                            [submission.title_text_display for submission in excluded]
                         ),
                     ),
                 )
diff --git a/hypha/apply/funds/management/commands/export_submissions_csv.py b/hypha/apply/funds/management/commands/export_submissions_csv.py
index af107417a..4014ec81e 100644
--- a/hypha/apply/funds/management/commands/export_submissions_csv.py
+++ b/hypha/apply/funds/management/commands/export_submissions_csv.py
@@ -64,7 +64,7 @@ class Command(BaseCommand):
                     submission_value = 0
                 writer.writerow(
                     [
-                        submission.id,
+                        submission.public_id or submission.id,
                         submission.title,
                         submission.full_name,
                         submission.email,
diff --git a/hypha/apply/funds/migrations/0120_applicationsubmission_public_id_and_more.py b/hypha/apply/funds/migrations/0120_applicationsubmission_public_id_and_more.py
new file mode 100644
index 000000000..ced2a2192
--- /dev/null
+++ b/hypha/apply/funds/migrations/0120_applicationsubmission_public_id_and_more.py
@@ -0,0 +1,44 @@
+# Generated by Django 4.2.11 on 2024-05-29 13:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        (
+            "funds",
+            "0119_rename_applicationbaseprojectapprovalform_applicationbaseprojectform_and_more",
+        ),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="applicationsubmission",
+            name="public_id",
+            field=models.CharField(
+                blank=True, db_index=True, max_length=255, null=True, unique=True
+            ),
+        ),
+        migrations.AddField(
+            model_name="labbase",
+            name="submission_id_prefix",
+            field=models.SlugField(
+                blank=True,
+                default="",
+                help_text='Prefix for the submission id. e.g. "HYPHA23-" will result in a submission id of "HYPHA23-1".',
+                max_length=10,
+                verbose_name="Submission ID Prefix",
+            ),
+        ),
+        migrations.AddField(
+            model_name="roundbase",
+            name="submission_id_prefix",
+            field=models.SlugField(
+                blank=True,
+                default="",
+                help_text='Prefix for the submission id. e.g. "HYPHA23-" will result in a submission id of "HYPHA23-1".',
+                max_length=10,
+                verbose_name="Submission ID Prefix",
+            ),
+        ),
+    ]
diff --git a/hypha/apply/funds/models/application_revisions.py b/hypha/apply/funds/models/application_revisions.py
index 3ee8d3800..516f91eae 100644
--- a/hypha/apply/funds/models/application_revisions.py
+++ b/hypha/apply/funds/models/application_revisions.py
@@ -29,7 +29,7 @@ class ApplicationRevision(BaseStreamForm, AccessFormData, models.Model):
         ordering = ["-timestamp"]
 
     def __str__(self):
-        return f"Revision for {self.submission.title} by {self.author} "
+        return f"Revision for {self.submission.title_text_display} by {self.author} "
 
     @property
     def form_fields(self):
diff --git a/hypha/apply/funds/models/applications.py b/hypha/apply/funds/models/applications.py
index 2fa80e184..5900d2075 100644
--- a/hypha/apply/funds/models/applications.py
+++ b/hypha/apply/funds/models/applications.py
@@ -211,6 +211,17 @@ class RoundBase(WorkflowStreamForm, SubmittableStreamForm):  # type: ignore
     # Adds validation for making start_date required
     base_form_class = RoundBasePageAdminForm
 
+    submission_id_prefix = models.SlugField(
+        _("Submission ID Prefix"),
+        max_length=10,
+        blank=True,
+        default="",
+        null=False,
+        help_text=_(
+            'Prefix for the submission id. e.g. "HYPHA23-" will result in a submission id of "HYPHA23-1".'
+        ),
+    )
+
     lead = models.ForeignKey(
         settings.AUTH_USER_MODEL,
         limit_choices_to=LIMIT_TO_STAFF,
@@ -260,6 +271,7 @@ class RoundBase(WorkflowStreamForm, SubmittableStreamForm):  # type: ignore
             heading=_("Dates"),
         ),
         FieldPanel("reviewers", widget=forms.CheckboxSelectMultiple),
+        FieldPanel("submission_id_prefix"),
         ReadOnlyPanel(
             "get_workflow_name_display",
             heading=_("Workflow"),
@@ -555,6 +567,16 @@ class LabBase(EmailForm, WorkflowStreamForm, SubmittableStreamForm):  # type: ig
         related_name="lab_lead",
         on_delete=models.PROTECT,
     )
+    submission_id_prefix = models.SlugField(
+        _("Submission ID Prefix"),
+        max_length=10,
+        blank=True,
+        default="",
+        null=False,
+        help_text=_(
+            'Prefix for the submission id. e.g. "HYPHA23-" will result in a submission id of "HYPHA23-1".'
+        ),
+    )
     reviewers = ParentalManyToManyField(
         settings.AUTH_USER_MODEL,
         related_name="labs_reviewer",
@@ -609,6 +631,7 @@ class LabBase(EmailForm, WorkflowStreamForm, SubmittableStreamForm):  # type: ig
         FieldPanel("image"),
         FieldPanel("weight"),
         FieldPanel("slack_channel"),
+        FieldPanel("submission_id_prefix"),
         FieldPanel("activity_digest_recipient_emails"),
         FieldPanel("list_on_front_page"),
     ]
diff --git a/hypha/apply/funds/models/submissions.py b/hypha/apply/funds/models/submissions.py
index 015d4d55a..62a8fa26b 100644
--- a/hypha/apply/funds/models/submissions.py
+++ b/hypha/apply/funds/models/submissions.py
@@ -28,6 +28,7 @@ from django.db.models.functions import Cast
 from django.dispatch import receiver
 from django.urls import reverse
 from django.utils import timezone
+from django.utils.html import strip_tags
 from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
 from django_fsm import RETURN_VALUE, FSMField, can_proceed, transition
@@ -412,6 +413,9 @@ class ApplicationSubmission(
 ):
     form_data = models.JSONField(encoder=StreamFieldDataEncoder)
     form_fields = StreamField(ApplicationCustomFormFieldsBlock(), use_json_field=True)
+    public_id = models.CharField(
+        max_length=255, null=True, blank=True, unique=True, db_index=True
+    )
     summary = models.TextField(default="", null=True, blank=True)
     page = models.ForeignKey("wagtailcore.Page", on_delete=models.PROTECT)
     round = models.ForeignKey(
@@ -509,6 +513,18 @@ class ApplicationSubmission(
     def is_draft(self):
         return self.status == DRAFT_STATE
 
+    @property
+    def title_text_display(self):
+        """Return the title text for display across the site.
+
+        Use SUBMISSION_TITLE_TEXT_TEMPLATE setting to change format.
+        """
+        ctx = {
+            "title": self.title,
+            "public_id": self.public_id or self.id,
+        }
+        return strip_tags(settings.SUBMISSION_TITLE_TEXT_TEMPLATE.format(**ctx))
+
     def not_progressed(self):
         return not self.next
 
@@ -613,6 +629,7 @@ class ApplicationSubmission(
         prev_meta_terms = submission_in_db.meta_terms.all()
 
         self.id = None
+        self.public_id = None
         proposal_form = kwargs.get("proposal_form")
         proposal_form = int(proposal_form) if proposal_form else 0
         self.form_fields = self.get_from_parent("get_defined_fields")(
@@ -756,11 +773,16 @@ class ApplicationSubmission(
 
         super().save(*args, **kwargs)
 
-        # TODO: This functionality should be extracted and moved to a seperate function, too hidden here
+        # TODO: This functionality should be extracted and moved to a separate function, too hidden here
         if creating:
             AssignedReviewers = apps.get_model("funds", "AssignedReviewers")
             ApplicationRevision = apps.get_model("funds", "ApplicationRevision")
 
+            if not self.public_id:
+                self.public_id = (
+                    f"{self.get_from_parent('submission_id_prefix')}{self.id}"
+                )
+
             self.process_file_data(files)
             AssignedReviewers.objects.bulk_create_reviewers(
                 list(self.get_from_parent("reviewers").all()),
@@ -863,13 +885,14 @@ class ApplicationSubmission(
         values = self.get_searchable_contents()
 
         # Add named fields into the search index
-        for field in ["full_name", "email", "title"]:
-            values.append(getattr(self, field))
+        for field in ["full_name", "email", "title", "public_id"]:
+            if value := getattr(self, field):
+                values.append(value)
         return values
 
     def index_components(self):
         return {
-            "A": " ".join([f"id:{self.id}", self.title]),
+            "A": " ".join([f"id:{self.public_id or self.id}", self.title]),
             "C": " ".join([self.full_name, self.email]),
             "B": " ".join(self.get_searchable_contents()),
         }
@@ -884,7 +907,7 @@ class ApplicationSubmission(
         return reverse("funds:submissions:detail", args=(self.id,))
 
     def __str__(self):
-        return f"{self.title} from {self.full_name} for {self.page.title}"
+        return f"{self.title_text_display} from {self.full_name} for {self.page.title}"
 
     def __repr__(self):
         return f"<{self.__class__.__name__}: {self.user}, {self.round}, {self.page}>"
diff --git a/hypha/apply/funds/tables.py b/hypha/apply/funds/tables.py
index 082b46832..4c34b3d1c 100644
--- a/hypha/apply/funds/tables.py
+++ b/hypha/apply/funds/tables.py
@@ -50,9 +50,9 @@ def render_actions(table, record):
 
 def render_title(record):
     try:
-        title = record.title
+        title = record.title_text_display
     except AttributeError:
-        title = record.submission.title
+        title = record.submission.title_text_display
     return title
 
 
@@ -73,7 +73,7 @@ class SubmissionsTable(tables.Table):
                 "class": "js-title",
             },
             "a": {
-                "data-tippy-content": lambda record: record.title,
+                "data-tippy-content": lambda record: render_title(record),
                 "data-tippy-placement": "top",
                 # Use after:content-[''] after:block to hide the default browser tooltip on Safari
                 # https://stackoverflow.com/a/43915246
@@ -692,7 +692,7 @@ class ReviewerLeaderboardDetailTable(tables.Table):
                 "class": "js-title",
             },
             "a": {
-                "data-tippy-content": lambda record: record.submission.title,
+                "data-tippy-content": lambda record: render_title(record),
                 "data-tippy-placement": "top",
                 # Use after:content-[''] after:block to hide the default browser tooltip on Safari
                 # https://stackoverflow.com/a/43915246
diff --git a/hypha/apply/funds/templates/funds/applicationrevision_list.html b/hypha/apply/funds/templates/funds/applicationrevision_list.html
index 437b118a4..178d03f9b 100644
--- a/hypha/apply/funds/templates/funds/applicationrevision_list.html
+++ b/hypha/apply/funds/templates/funds/applicationrevision_list.html
@@ -5,7 +5,7 @@
 {% block content %}
     {% adminbar %}
         {% slot header %}{% trans "Revisions" %}{% endslot %}
-        {% slot sub_heading %}{% trans "For" %} <a class="text-blue-300 hover:underline" href="{% url "funds:submissions:detail" submission.id %}">{{ submission.title }}</a>{% endslot %}
+        {% slot sub_heading %}{% trans "For" %} <a class="text-blue-300 hover:underline" href="{% url "funds:submissions:detail" submission.id %}">{{ submission.title_text_display }}</a>{% endslot %}
     {% endadminbar %}
 
     <div class="wrapper wrapper--medium">
diff --git a/hypha/apply/funds/templates/funds/applicationsubmission_confirm_delete.html b/hypha/apply/funds/templates/funds/applicationsubmission_confirm_delete.html
index e939a5eb8..a2ed798dd 100644
--- a/hypha/apply/funds/templates/funds/applicationsubmission_confirm_delete.html
+++ b/hypha/apply/funds/templates/funds/applicationsubmission_confirm_delete.html
@@ -1,19 +1,19 @@
 {% extends "base-apply.html" %}
 {% load i18n static %}
 
-{% block title %}{% trans "Deleting" %}: {{object.title }}{% endblock %}
+{% block title %}{% trans "Deleting" %}: {{object.title_text_display }}{% endblock %}
 
 {% block content %}
 
     {% adminbar %}
-        {% slot header %}{% trans "Deleting" %}: {{ object.title }}{% endslot %}
+        {% slot header %}{% trans "Deleting" %}: {{ object.title }} <span class="text-gray-400">#{{object.public_id|default:object.id}}</span>{% endslot %}
     {% endadminbar %}
 
     <div class="wrapper wrapper--light-grey-bg wrapper--form wrapper--sidebar">
         <div class="wrapper--sidebar--inner">
             <form class="form" action="" method="post">
                 {% csrf_token %}
-                <p><strong>{% blocktrans %}Are you sure you want to delete "{{ object }}"?{% endblocktrans %}</strong></p>
+                <p><strong>{% blocktrans %}Are you sure you want to delete {{ object }}?{% endblocktrans %}</strong></p>
                 <p>{% trans "All content related to this submission will also be deleted. This includes reviews, determinations and comments." %}</p>
                 <button class="button button--warning button--submit button--top-space" type="submit">{% trans "Confirm" %}</button>
             </form>
diff --git a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html
index 798291630..96a1b6f58 100644
--- a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html
+++ b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html
@@ -2,7 +2,7 @@
 {% load i18n static workflow_tags wagtailcore_tags statusbar_tags archive_tags %}
 {% load heroicons %}
 
-{% block title %}{{ object.title }}{% endblock %}
+{% block title %}#{{ object.public_id|default_if_none:object.id}}: {{ object.title }}{% endblock %}
 {% block body_class %}{% endblock %}
 {% block content %}
     {% if object.round.specific.is_sealed %}
@@ -21,7 +21,7 @@
                     {% trans "Back to submissions" %}
                 </a>
             {% endif %}
-            <h1 class="mb-0 mt-2 font-medium">{{ object.title }}</h1>
+            <h1 class="mb-0 mt-2 font-medium">{{ object.title }}<span class="text-gray-400"> #{{ object.public_id|default:object.id }}</span></h1>
             <div class="heading heading--meta text-sm mt-1 font-medium">
                 <span>{{ object.stage }}</span>
                 <span>{{ object.page }}</span>
@@ -175,7 +175,15 @@
                                         <h6 class="heading heading--light-grey heading--uppercase">{% trans "Past Submissions" %}</h6>
                                         <ul>
                                     {% endif %}
-                                    <li><a class="link link--underlined link--bold" href="{% url 'funds:submissions:detail' submission.id %}">{{ submission.title }}</a></li>
+                                    <li>
+                                        <a class="hover:underline" href="{% url 'funds:submissions:detail' submission.id %}">
+                                            <span class="link link--bold">
+                                                {{ submission.title }}
+                                            </span>
+                                            <span class="text-gray-400 text-sm font-semibold">#{{ submission.public_id|default:submission.id }}</span>
+                                        </a>
+
+                                    </li>
                                     {% if forloop.last %}
                                         </ul>
                                     {% endif %}
diff --git a/hypha/apply/funds/templates/funds/applicationsubmission_form.html b/hypha/apply/funds/templates/funds/applicationsubmission_form.html
index 673bdcc32..47b1372e1 100644
--- a/hypha/apply/funds/templates/funds/applicationsubmission_form.html
+++ b/hypha/apply/funds/templates/funds/applicationsubmission_form.html
@@ -1,11 +1,11 @@
 {% extends "base-apply.html" %}
 {% load i18n static %}
-{% block title %}{% trans "Editing" %}: {{object.title }}{% endblock %}
+{% block title %}{% trans "Editing" %}: {% if object.public_id %}{{ object.public_id }}: {% endif %}{{object.title }}{% endblock %}
 {% block body_class %}bg-white{% endblock %}
 
 {% block content %}
     {% adminbar %}
-        {% slot header %}{% trans "Editing" %}: {{ object.title }}{% endslot %}
+        {% slot header %}{% trans "Editing" %}: {{ object.title }} <span class="text-gray-400">#{{ object.public_id|default:object.id }}</span>{% endslot %}
     {% endadminbar %}
 
     {% include "forms/includes/form_errors.html" with form=form %}
diff --git a/hypha/apply/funds/templates/funds/includes/rendered_answers.html b/hypha/apply/funds/templates/funds/includes/rendered_answers.html
index 1f28aab99..62ac8603d 100644
--- a/hypha/apply/funds/templates/funds/includes/rendered_answers.html
+++ b/hypha/apply/funds/templates/funds/includes/rendered_answers.html
@@ -25,13 +25,13 @@
         {{ object.get_email_display }}
     </div>
     {% if object.get_address_display != "-" %}
-        <div class="grid__cell--span-two">
+        <div>
             <h5 class="text-base">{% trans "Address" %}</h5>
             {{ object.get_address_display }}
         </div>
     {% endif %}
     {% if object.get_organization_name_display != "-" %}
-        <div class="grid__cell--span-two">
+        <div>
             <h5 class="text-base">{% trans "Organization name" %}</h5>
             {{ object.get_organization_name_display }}
         </div>
diff --git a/hypha/apply/funds/templates/funds/includes/submission-list-item.html b/hypha/apply/funds/templates/funds/includes/submission-list-item.html
index d435f9e91..f4c2a620d 100644
--- a/hypha/apply/funds/templates/funds/includes/submission-list-item.html
+++ b/hypha/apply/funds/templates/funds/includes/submission-list-item.html
@@ -70,7 +70,7 @@
 
         <div class="pt-1">
             <p class="text-xs m-0">
-                #{{ s.id }}
+                #{{ s.public_id|default:s.id }}
                 submitted <relative-time datetime="{{ s.submit_time|date:"c" }}">{{ s.submit_time|date:"SHORT_DATE_FORMAT" }}</relative-time>
                 by <a
                     href="?applicants={{ s.user.id }}"
diff --git a/hypha/apply/funds/templates/funds/revisions_compare.html b/hypha/apply/funds/templates/funds/revisions_compare.html
index f71d74138..431bdb1df 100644
--- a/hypha/apply/funds/templates/funds/revisions_compare.html
+++ b/hypha/apply/funds/templates/funds/revisions_compare.html
@@ -12,7 +12,7 @@
 
         {% slot header %}{% trans "Comparing revisions" %}{% endslot %}
         {% slot sub_heading %}
-            {% trans "For" %} <a class="text-blue-300 hover:underline" href="{% url "funds:submissions:detail" object.id %}">{{ object.title }}</a>
+            {% trans "For" %} <a class="text-blue-300 hover:underline" href="{% url "funds:submissions:detail" object.id %}">{{ object.title_text_display }}</a>
         {% endslot %}
 
     {% endadminbar %}
diff --git a/hypha/apply/funds/templatetags/submission_tags.py b/hypha/apply/funds/templatetags/submission_tags.py
index db565999e..0710a56ad 100644
--- a/hypha/apply/funds/templatetags/submission_tags.py
+++ b/hypha/apply/funds/templatetags/submission_tags.py
@@ -1,6 +1,7 @@
 import re
 
 from django import template
+from django.db.models import Q
 from django.utils.safestring import mark_safe
 
 from hypha.apply.funds.models import ApplicationSubmission
@@ -10,13 +11,17 @@ register = template.Library()
 
 @register.filter
 def submission_links(value):
-    # Match tags in the format #123 that is not preceeded and/or followed by a word character.
-    matches = re.findall(r"(?<![\w\&])\#(\d+)(?!\w)", value)
+    # regex to find #id in a string, which id can be alphanumeric, underscore, hyphen
+    matches = re.findall(r"(?<![\w\&])\#([\w-]+)(?!\w)", value)
     links = {}
     if matches:
-        for submission in ApplicationSubmission.objects.filter(id__in=matches):
-            links[rf"\#{submission.id}"] = (
-                f'<a href="{submission.get_absolute_url()}">{submission.title} <span class="mid-grey-text">#{submission.id}</span></a>'
+        numeric_ids = filter(str.isdigit, matches)
+        qs = ApplicationSubmission.objects.filter(
+            Q(id__in=numeric_ids) | Q(public_id__in=matches)
+        )
+        for submission in qs:
+            links[rf"\#{submission.public_id or submission.id}"] = (
+                f'<a href="{submission.get_absolute_url()}">{submission.title} <span class="text-gray-400">#{submission.public_id or submission.id}</span></a>'
             )
 
     if links:
diff --git a/hypha/apply/funds/tests/test_tags.py b/hypha/apply/funds/tests/test_tags.py
index 5571a002f..c85a5408a 100644
--- a/hypha/apply/funds/tests/test_tags.py
+++ b/hypha/apply/funds/tests/test_tags.py
@@ -18,9 +18,11 @@ class TestTemplateTags(TestCase):
         template = Template(
             "{% load submission_tags %}{{ content|submission_links|safe }}"
         )
-        context = Context({"content": f"Lorem ipsum dolor #{submission.id} sit amet."})
+        context = Context(
+            {"content": f"Lorem ipsum dolor #{submission.public_id} sit amet."}
+        )
         output = template.render(context)
-        self.assertEqual(
-            output,
-            f'Lorem ipsum dolor <a href="{submission.get_absolute_url()}">{submission.title} <span class="mid-grey-text">#{submission.id}</span></a> sit amet.',
+        assert (
+            output
+            == f'Lorem ipsum dolor <a href="{submission.get_absolute_url()}">{submission.title} <span class="text-gray-400">#{submission.public_id or submission.id}</span></a> sit amet.'
         )
diff --git a/hypha/apply/review/templates/review/review_list.html b/hypha/apply/review/templates/review/review_list.html
index 272f0b560..039ecc0b5 100644
--- a/hypha/apply/review/templates/review/review_list.html
+++ b/hypha/apply/review/templates/review/review_list.html
@@ -8,7 +8,7 @@
 {% block content %}
     {% adminbar %}
         {% slot header %}{% trans "Reviews" %}{% endslot %}
-        {% slot sub_heading %}{% trans "For" %} <a class="text-blue-200" href="{% url "funds:submissions:detail" submission.id %}">{{ submission.title }}</a>{% endslot %}
+        {% slot sub_heading %}{% trans "For" %} <a class="text-blue-200" href="{% url "funds:submissions:detail" submission.id %}">{{ submission.title_text_display }}</a>{% endslot %}
 
         {% if request.user|has_review_perm:submission %}
             {% if request.user|has_draft:submission or request.user|can_review:submission %}
diff --git a/hypha/settings/base.py b/hypha/settings/base.py
index 044aee86c..bf73abfc5 100644
--- a/hypha/settings/base.py
+++ b/hypha/settings/base.py
@@ -62,6 +62,11 @@ SUBMISSIONS_ARCHIVED_VIEW_ACCESS_STAFF_ADMIN = env.bool(
     "SUBMISSIONS_ARCHIVED_ACCESS_STAFF_ADMIN", True
 )
 
+# Possible values are: "public_id" and "title"
+SUBMISSION_TITLE_TEXT_TEMPLATE = env(
+    "SUBMISSION_TITLE_TEMPLATE", default="{title} (#{public_id})"
+)
+
 # Provide permissions for archiving submissions
 SUBMISSIONS_ARCHIVED_ACCESS_STAFF = env.bool("SUBMISSIONS_ARCHIVED_ACCESS_STAFF", False)
 SUBMISSIONS_ARCHIVED_ACCESS_STAFF_ADMIN = env.bool(
@@ -433,6 +438,7 @@ NH3_ALLOWED_ATTRIBUTES = {
         "target",
         "title",
         "width",
+        "data-tippy-content",
     ]
 }
 
diff --git a/hypha/static_src/javascript/batch-actions.js b/hypha/static_src/javascript/batch-actions.js
index e94bd4d67..d900d749d 100644
--- a/hypha/static_src/javascript/batch-actions.js
+++ b/hypha/static_src/javascript/batch-actions.js
@@ -108,7 +108,7 @@
         $checkbox.filter(":checked").each(function () {
             const link = $(this).parents("tr").find(".js-title").find("a");
             const href = link.attr("href");
-            const title = link.data("tippy-content");
+            const title = link.text();
 
             $batchTitlesList.append(`
                 <a href="${href}" class="list-reveal__item" target="_blank" rel="noopener noreferrer" title="${title}">
diff --git a/hypha/static_src/sass/base/_base.scss b/hypha/static_src/sass/base/_base.scss
index a34361a04..0d48e7a4c 100644
--- a/hypha/static_src/sass/base/_base.scss
+++ b/hypha/static_src/sass/base/_base.scss
@@ -84,7 +84,3 @@ details > summary {
     position: relative;
     inset-inline-start: 0;
 }
-
-.mid-grey-text {
-    color: $color--mid-dark-grey;
-}
-- 
GitLab