Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7214f07
Initial implementation for a comments sidebar on reports, invoices, P…
wes-otf Jul 29, 2026
deabb5f
Removed unneeded InvoiceDetailView
wes-otf Jul 29, 2026
5d75777
Fix PAF status retrieval issues, handle no fetch URL being provided
wes-otf Jul 29, 2026
4eff25d
Removed CommentFormMini in favor of using init args
wes-otf Jul 30, 2026
537e2ce
fixed migrations
wes-otf Jul 31, 2026
f3cb71b
Squashed migrations
wes-otf Aug 5, 2026
e0ab374
Added more unit tests
wes-otf Aug 5, 2026
e4a3673
Various fixes: changed `an` tag to be more verbose + unit test adds, …
wes-otf Aug 13, 2026
892893b
Fix translations issues.
frjo Aug 19, 2026
4d1304d
Fix translating an_or_a.
frjo Aug 19, 2026
b7a290d
Missing translation.
frjo Aug 21, 2026
32e674a
Create get_object_for_content_type support function to avoid duplicat…
frjo Aug 25, 2026
7a6d639
Move support functions _get_object_activity and _get_object_for_conte…
frjo Aug 25, 2026
9e6681a
Missing translation.
frjo Aug 25, 2026
793c2fd
Add @require_GET to all partial views.
frjo Aug 25, 2026
56b666f
Make sure partials only show content the user are allowed to see.
frjo Aug 25, 2026
47b53d8
Fix annotation on _get_object_activity.
frjo Aug 25, 2026
d33bf9e
Field related_content_type already renderd by hidden_fields.
frjo Aug 25, 2026
5f981d9
Only show comment form to staff.
frjo Aug 25, 2026
cb52812
linting fixes
wes-otf Aug 31, 2026
04dd1c3
Merge branch 'main' into feature/comments-on-specific-objects
wes-otf Sep 1, 2026
72eaffb
Fix invoice test URL
wes-otf Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions hypha/apply/activity/adapters/activity_feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class ActivityAdapter(AdapterBase):
MESSAGES.CREATED_PROJECT: _(
'Created project with initial status of "{status}"'
),
MESSAGES.CREATED_SOW: _('Created SOW for project "{related.project}"'),
MESSAGES.EDITED_SOW: _('Edited SOW for project "{related.project}"'),
MESSAGES.CREATED_PF: _('Created project form for project "{related.project}"'),
MESSAGES.EDITED_PF: _('Edited project form for project "{related.project}"'),
MESSAGES.PROJECT_TRANSITION: "handle_project_transition",
MESSAGES.UPDATE_PROJECT_TITLE: _(
"updated the project title from {old_title} to {source.title}"
Expand Down Expand Up @@ -93,6 +97,10 @@ def extra_kwargs(self, message_type, source, sources, **kwargs):
MESSAGES.DELETE_REVIEW_OPINION,
MESSAGES.BATCH_REVIEWERS_UPDATED,
MESSAGES.APPROVE_PROJECT,
MESSAGES.CREATED_SOW,
MESSAGES.EDITED_SOW,
MESSAGES.CREATED_PF,
MESSAGES.EDITED_PF,
MESSAGES.REQUEST_PROJECT_CHANGE,
MESSAGES.SEND_FOR_APPROVAL,
MESSAGES.APPROVE_PAF,
Expand Down
4 changes: 4 additions & 0 deletions hypha/apply/activity/adapters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
MESSAGES.DELETE_REVIEW_OPINION: "review_opinion",
MESSAGES.EDIT_REVIEW: "review",
MESSAGES.CREATED_PROJECT: "submission",
MESSAGES.CREATED_SOW: "sow",
MESSAGES.EDITED_SOW: "sow",
MESSAGES.EDITED_PF: "pfp",
MESSAGES.CREATED_PF: "pfp",
MESSAGES.PROJECT_TRANSITION: "old_stage",
MESSAGES.APPROVE_PAF: "paf_approvals", # expect a list
MESSAGES.UPDATE_PROJECT_LEAD: "old_lead",
Expand Down
26 changes: 21 additions & 5 deletions hypha/apply/activity/forms.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django import forms
from django.db import transaction
from django.forms.widgets import Textarea
from django.utils.translation import gettext_lazy as _
from django_file_form.forms import FileFormMixin

Expand All @@ -24,11 +25,16 @@ class CommentForm(FileFormMixin, forms.ModelForm):

class Meta:
model = Activity
fields = (
"message",
"visibility",
"assign_to",

# Fields that should only be included when the mini comment form is used
# as the mini form can be put anywhere and associated to any object.
mini_fields = (
"related_content_type",
"related_object_id",
"source_content_type",
"source_object_id",
Comment thread
wes-otf marked this conversation as resolved.
)
fields = ("message", "visibility", "assign_to", *mini_fields)
labels = {
"visibility": _("Visible to"),
"message": _("Message"),
Expand All @@ -41,9 +47,10 @@ class Meta:
widgets = {
"visibility": forms.RadioSelect(),
"message": PagedownWidget(),
**{field: forms.HiddenInput() for field in mini_fields},
}

def __init__(self, *args, user=None, has_coapplicants=False, **kwargs):
def __init__(self, *args, user=None, has_coapplicants=False, mini=False, **kwargs):
super().__init__(*args, **kwargs)
self.visibility_choices = self._meta.model.visibility_choices_for(
user, has_coapplicants
Expand All @@ -61,6 +68,15 @@ def __init__(self, *args, user=None, has_coapplicants=False, **kwargs):
if not user.is_apply_staff:
self.fields["assign_to"].widget = forms.HiddenInput()

if mini:
self.fields["message"].widget = Textarea(
attrs={"rows": 2, "placeholder": _("Write a comment...")}
)
else:
# If not mini, remove the unneeded fields from the form.
for key in self.Meta.mini_fields:
del self.fields[key]

@transaction.atomic
def save(self, commit=True):
instance = super().save(commit=True)
Expand Down
6 changes: 5 additions & 1 deletion hypha/apply/activity/migrations/0096_alter_event_type.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 5.2.17 on 2026-08-25 18:01
# Generated by Django 5.2.17 on 2026-08-31 14:08

from django.db import migrations, models

Expand Down Expand Up @@ -40,6 +40,10 @@ class Migration(migrations.Migration):
("DELETE_REVIEW", "deleted review"),
("DELETE_REVIEW_OPINION", "deleted review opinion"),
("CREATED_PROJECT", "created project"),
("CREATED_SOW", "created a project SOW"),
("EDITED_SOW", "edited a project SOW"),
("CREATED_PF", "created a project form"),
("EDITED_PF", "edited a project form"),
("UPDATE_PROJECT_LEAD", "updated project lead"),
("UPDATE_PROJECT_TITLE", "updated project title"),
(
Expand Down
4 changes: 4 additions & 0 deletions hypha/apply/activity/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class MESSAGES(TextChoices):
DELETE_REVIEW = "DELETE_REVIEW", _("deleted review")
DELETE_REVIEW_OPINION = "DELETE_REVIEW_OPINION", _("deleted review opinion")
CREATED_PROJECT = "CREATED_PROJECT", _("created project")
CREATED_SOW = "CREATED_SOW", _("created a project SOW")
EDITED_SOW = "EDITED_SOW", _("edited a project SOW")
CREATED_PF = "CREATED_PF", _("created a project form")
EDITED_PF = "EDITED_PF", _("edited a project form")
UPDATE_PROJECT_LEAD = "UPDATE_PROJECT_LEAD", _("updated project lead")
UPDATE_PROJECT_TITLE = "UPDATE_PROJECT_TITLE", _("updated project title")
UPDATE_PROJECT_CONTRACT_NUMBER = (
Expand Down
37 changes: 37 additions & 0 deletions hypha/apply/activity/templates/activity/partials/comment_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% comment %}
Renders the sidebar comments form.

Params:
form – the CommentForm form
{% endcomment %}

{% load i18n static heroicons %}

{# On error the view re-renders this whole form, so swap outerHTML on self to avoid nesting a form inside itself. A successful post returns 204 and is not swapped. #}
<form hx-post="{% url 'activity:post-comment' %}" hx-target="this" hx-swap="outerHTML" class="flex flex-col gap-2 p-2 card-body" hx-on::after-request="if(event.detail.xhr.status === 204) this.reset()">
<h2 class="card-title">{% trans "Comment" %}</h2>
{% csrf_token %}

{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<div>
{% include "forms/includes/field.html" with field=form.message label_classes="sr-only" %}
</div>

<details class="mb-1 collapse bg-base-100 group">
<summary class="flex gap-1.5 items-center text-sm collapse-title"><span>{% trans 'Additional options' %}</span>{% heroicon_mini 'chevron-down' class="size-5 group-open:-rotate-180" %}</summary>
{# Some small modifications to django-file-form to make it fit better with the smaller modal #}
<div class="collapse-content [&_.dff-uploader]:hidden [&_.btn-upload]:w-full">
{% include "forms/includes/field.html" with field=form.visibility %}
{% include "forms/includes/field.html" with field=form.assign_to %}
{% include "forms/includes/field.html" with field=form.attachments %}
</div>
</details>
<button
class="btn btn-primary"
type="submit"
>
{% trans "Add Comment" %}
</button>
</form>
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
{% comment %}
Renders a line item of activity including the message attached to the activity & an icon based on the message.

Params:
activity – an Activity object to render into a timeline
no_timeline - bool of should the line item be styled as a piece of a larger timeline
mini - bool that will force including only essential elements of the action item
{% endcomment %}

{% load i18n activity_tags heroicons %}

{% with activity|display_for:request.user as activity_text %}
<div class="relative h-timeline-item" id="communications#{{ activity.id }}">
<div class="{% if not no_timeline %}relative h-timeline-item{% endif %}" id="communications#{{ activity.id }}">
<div
class="flex items-center py-2 -ml-3 before:block before:absolute before:top-0 before:bottom-0 before:left-0 before:w-(--border) before:bg-base-300"
>
Expand All @@ -22,6 +31,12 @@
{% heroicon_micro "lock-closed" class="inline" aria_hidden=true size=14 %}
{% elif 'lead' in activity_text.lower or 'author' in activity_text.lower %}
{% heroicon_micro "users" class="inline" aria_hidden=true size=14 %}
{% elif 'approved by' in activity_text.lower %}
Comment thread
wes-otf marked this conversation as resolved.
{% heroicon_micro "check-circle" class="inline" aria_hidden=true size=14 %}
{% elif 'changes requested' in activity_text.lower %}
{% heroicon_micro "exclamation-circle" class="inline" aria_hidden=true size=14 %}
{% elif 'created' in activity_text.lower %}
{% heroicon_micro "folder-plus" class="inline" aria_hidden=true size=14 %}
{% else %}
{% heroicon_micro "eye" class="inline" aria_hidden=true size=15 %}
{% endif %}
Expand All @@ -39,7 +54,7 @@
{{ activity.timestamp|date:'SHORT_DATETIME_FORMAT' }}
</relative-time>
</span>
{% if not submission_title and activity|user_can_see_related:request.user %}
{% if not submission_title and activity|user_can_see_related:request.user and not mini %}
{% with url=activity.related_object.get_absolute_url %}
{% if url %}
<a href="{{ url }}" class="link">
Expand Down
Loading