Skip to content

Commit adbeb65

Browse files
committed
Use Passward reset token generator, add auto login and signup feature on accept, Updated permissions and dashboard for co-applicants, Update listing and modals UI
1 parent 4e019ac commit adbeb65

24 files changed

Lines changed: 572 additions & 159 deletions

hypha/apply/activity/adapters/emails.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from django.conf import settings
77
from django.contrib.auth import get_user_model
88
from django.template.loader import render_to_string
9-
from django.urls import reverse
109
from django.utils.translation import gettext as _
1110

1211
from hypha.apply.activity import tasks
@@ -169,26 +168,14 @@ def handle_transition(self, old_phase, source, **kwargs):
169168
)
170169

171170
def handle_co_applicant_invite(self, source, related, **kwargs):
172-
from hypha.apply.funds.utils import generate_signed_token
171+
from hypha.apply.funds.utils import generate_invite_path
173172

174173
invited_user = User.objects.filter(email=related.invited_user_email).first()
175174
can_accept = True
176-
if invited_user and (
177-
invited_user.is_apply_staff or invited_user.is_apply_staff_admin
178-
):
175+
if invited_user and (invited_user.is_org_faculty):
179176
can_accept = False
180177

181-
token = generate_signed_token(
182-
data={
183-
"email": related.invited_user_email,
184-
"submission": related.submission.pk,
185-
},
186-
salt="co-applicant-invite-token",
187-
)
188-
accept_link = reverse(
189-
"apply:submissions:accept_coapplicant_invite",
190-
kwargs={"pk": source.id, "token": token},
191-
)
178+
accept_link = generate_invite_path(invite=related)
192179
return self.render_message(
193180
"messages/email/invite_co_applicant.html",
194181
source=source,
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
{% extends "messages/email/applicant_base.html" %}
2-
1+
{% extends "messages/email/base.html" %}
32
{% load i18n %}
3+
{% block salutation %}{% blocktrans with name=related.invited_user_email|email_name %}Dear {{ name }},{% endblocktrans %}{% endblock %}
4+
45
{% block content %}{# fmt:off #}
56
{% blocktrans %}You have been invited as a co-applicant to an application on {{ ORG_SHORT_NAME }} by {{ user }}.{% endblocktrans %}
67
{% if not can_accept %}
78
{% trans "But You can't accept this invite because you already hold a responsible position in" %} {{ ORG_SHORT_NAME }}
89
{% endif %}
9-
{% blocktrans %} Click on link if you want to accept it, otherwise leave it.{% endblocktrans %}
10+
{% blocktrans %} Click on link if you want to accept it.{% endblocktrans %}
1011
{% trans "Link" %}: {{ request.scheme }}://{{ request.get_host }}{{ accept_link }}
1112
{% endblock %}{# fmt:on #}

hypha/apply/activity/templatetags/activity_tags.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,10 @@ def source_type(value) -> str:
180180
def lowerfirst(value):
181181
"""Lowercase the first character of the value."""
182182
return value and value[0].lower() + value[1:]
183+
184+
185+
@register.filter
186+
def email_name(email):
187+
if isinstance(email, str) and "@" in email:
188+
return email.split("@")[0]
189+
return email

hypha/apply/dashboard/views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from django.conf import settings
2+
from django.db.models import Q
23
from django.http import HttpResponseForbidden, HttpResponseRedirect
34
from django.shortcuts import render
45
from django.urls import reverse, reverse_lazy
@@ -462,7 +463,7 @@ class ApplicantDashboardView(TemplateView):
462463
def get_context_data(self, **kwargs):
463464
context = super().get_context_data(**kwargs)
464465
context["my_submissions_exists"] = ApplicationSubmission.objects.filter(
465-
user=self.request.user
466+
Q(user=self.request.user) | Q(co_applicants__user=self.request.user)
466467
).exists()
467468

468469
# Number of items to show in skeleton in each section of lazy loading

hypha/apply/dashboard/views_partials.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.contrib.auth.decorators import login_required
22
from django.core.paginator import Paginator
3-
from django.db.models import Case, When
3+
from django.db.models import Case, Q, When
44
from django.shortcuts import render
55
from django.views.decorators.http import require_GET
66

@@ -12,13 +12,14 @@
1212
def my_active_submissions(user):
1313
active_subs = (
1414
ApplicationSubmission.objects.filter(
15-
user=user,
15+
Q(user=user) | Q(co_applicants__user=user),
1616
)
1717
.annotate(
1818
is_active=Case(When(status__in=active_statuses, then=True), default=False)
1919
)
2020
.select_related("draft_revision")
2121
.order_by("-is_active", "-submit_time")
22+
.distinct()
2223
)
2324
for submission in active_subs:
2425
yield submission.from_draft()

hypha/apply/funds/forms.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
from .models import (
1717
ApplicationSubmission,
1818
AssignedReviewers,
19+
CoApplicant,
1920
CoApplicantInvite,
2021
Reminder,
2122
ReviewerRole,
2223
)
24+
from .models.co_applicants import COAPPLICANT_ROLE_CHOICES
2325
from .permissions import can_change_external_reviewers
2426
from .utils import model_form_initial, render_icon
2527
from .widgets import MetaTermWidget, MultiCheckboxesWidget
@@ -477,3 +479,16 @@ def __init__(self, *args, submission, user=None, **kwargs):
477479
class Meta:
478480
model = CoApplicantInvite
479481
fields = ["invited_user_email", "submission"]
482+
483+
484+
class EditCoApplicantForm(forms.ModelForm):
485+
role = forms.ChoiceField(
486+
choices=COAPPLICANT_ROLE_CHOICES, label="Role", required=False
487+
)
488+
489+
def __int__(self, *args, **kwargs):
490+
super().__init__(*args, **kwargs)
491+
492+
class Meta:
493+
model = CoApplicant
494+
fields = ("role",)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Generated by Django 4.2.20 on 2025-05-07 12:57
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("funds", "0125_remove_coapplicant_accepted_on_and_more"),
9+
]
10+
11+
operations = [
12+
migrations.AlterField(
13+
model_name="coapplicant",
14+
name="role",
15+
field=models.CharField(
16+
choices=[
17+
("read_only", "Read Only"),
18+
("comment", "Comment"),
19+
("full_access", "Full Access"),
20+
],
21+
default="read_only",
22+
),
23+
),
24+
]

hypha/apply/funds/models/co_applicants.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
from django.db import models
2+
from django.utils.translation import gettext_lazy as _
23

34
from hypha.apply.users.models import User
45

56
READ_ONLY = "read_only"
67
COMMENT = "comment"
8+
FULL_ACCESS = "full_access"
9+
10+
COAPPLICANT_ROLE_CHOICES = (
11+
(READ_ONLY, _("Read Only")),
12+
(COMMENT, _("Comment")),
13+
(FULL_ACCESS, _("Full Access")),
14+
)
715

816
COAPPLICANT_ROLE_PERM = {
917
READ_ONLY: "can_view",
@@ -59,7 +67,7 @@ class CoApplicant(models.Model):
5967
invite = models.OneToOneField(
6068
CoApplicantInvite, on_delete=models.CASCADE, related_name="co_applicant"
6169
)
62-
role = models.JSONField(default=list)
70+
role = models.CharField(choices=COAPPLICANT_ROLE_CHOICES, default=READ_ONLY)
6371
created_at = models.DateTimeField(auto_now_add=True, null=True)
6472

6573
class Meta:

hypha/apply/funds/permissions.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from django.core.exceptions import PermissionDenied
33
from rolepermissions.permissions import register_object_checker
44

5+
from hypha.apply.funds.models.co_applicants import COMMENT, READ_ONLY, CoApplicant
56
from hypha.apply.funds.models.submissions import DRAFT_STATE
67

78
from ..users.roles import STAFF_GROUP_NAME, SUPERADMIN, TEAMADMIN_GROUP_NAME, StaffAdmin
@@ -24,7 +25,17 @@ def can_edit_submission(user, submission):
2425
if submission.is_archive:
2526
return False, "Archived Submission"
2627

27-
return True, ""
28+
if submission.phase.permissions.can_edit(user):
29+
co_applicant = submission.co_applicants.filter(user=user).first()
30+
if co_applicant:
31+
if co_applicant.role not in [READ_ONLY, COMMENT]:
32+
return (
33+
True,
34+
"Co-applicant with read only or comment access can't edit submission",
35+
)
36+
return False, ""
37+
return True, "User can edit in current phase"
38+
return False, ""
2839

2940

3041
@register_object_checker()
@@ -220,10 +231,37 @@ def can_invite_co_applicants(user, submission):
220231
return False, "Forbidden Error"
221232

222233

234+
def can_view_co_applicants(user, submission):
235+
if user.is_applicant and user == submission.user:
236+
return True, "Submission user can access their submission's co-applicants"
237+
if user.is_apply_staff:
238+
return True, "Staff can access each submissions' co-applicants"
239+
return False, "Forbidden Error"
240+
241+
242+
def can_update_co_applicant(user, invite):
243+
if invite.invited_by == user:
244+
return True, "Same user who invited can delete the co-applicant"
245+
if invite.submission.user == user:
246+
return True, "Submission owner can delete the co-applicant"
247+
if user.is_apply_staff:
248+
return True, "Staff can delete any co-applicant of any submission"
249+
return False, "Forbidden Error"
250+
251+
252+
def user_can_view_post_comment_form(user, submission):
253+
co_applicant = CoApplicant.objects.filter(user=user, submission=submission).first()
254+
if co_applicant and co_applicant.role == READ_ONLY:
255+
return False
256+
return True
257+
258+
223259
permissions_map = {
224260
"submission_view": is_user_has_access_to_view_submission,
225261
"submission_edit": can_edit_submission,
226262
"can_view_submission_screening": can_view_submission_screening,
227263
"archive_alter": can_alter_archived_submissions,
228264
"co_applicant_invite": can_invite_co_applicants,
265+
"co_applicants_view": can_view_co_applicants,
266+
"co_applicants_update": can_update_co_applicant,
229267
}

hypha/apply/funds/templates/funds/applicationsubmission_detail.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends "base-apply.html" %}
2-
{% load i18n static workflow_tags wagtailcore_tags statusbar_tags archive_tags submission_tags translate_tags %}
2+
{% load i18n static workflow_tags wagtailcore_tags statusbar_tags archive_tags submission_tags translate_tags primaryactions_tags %}
33
{% load heroicons %}
44
{% load can from permission_tags %}
55

@@ -177,6 +177,13 @@ <h5>{% blocktrans with stage=object.previous.stage %}Your {{ stage }} applicatio
177177
{% block reminders %}
178178
{% endblock %}
179179

180+
{% display_coapplicant_section user object as coapplicant_section %}
181+
{% if coapplicant_section %}
182+
{% block co_applicant %}
183+
<div hx-trigger="revealed, coApplicantUpdated from:body" hx-get="{% url 'funds:submissions:partial_coapplicant_invites' object.id %}"></div>
184+
{% endblock %}
185+
{% endif %}
186+
180187
{% block related %}
181188
{% if other_submissions or object.previous or object.next %}
182189
<div class="sidebar__inner">

0 commit comments

Comments
 (0)