Skip to content

Commit

Permalink
add app alert creation (#1090)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Jan 20, 2025
1 parent 39cfdd1 commit dc56003
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 40 deletions.
2 changes: 1 addition & 1 deletion projectroles/app_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
),
description=(
'Receive email notifications for {} or {} creation, updating, '
'moving and archiving.'.format(
'moving, archiving and deletion.'.format(
get_display_name(PROJECT_TYPE_CATEGORY),
get_display_name(PROJECT_TYPE_PROJECT),
)
Expand Down
32 changes: 29 additions & 3 deletions projectroles/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2614,17 +2614,19 @@ def _get_delete_alerts(self):

def setUp(self):
super().setUp()
self.user_owner_cat = self.make_user('user_owner_cat')
self.user_owner = self.make_user('user_owner')
self.category = self.make_project(
'TestCategory', PROJECT_TYPE_CATEGORY, None
)
self.owner_as_cat = self.make_assignment(
self.category, self.user, self.role_owner
self.category, self.user_owner_cat, self.role_owner
)
self.project = self.make_project(
'TestProject', PROJECT_TYPE_PROJECT, self.category
)
self.owner_as = self.make_assignment(
self.project, self.user, self.role_owner
self.project, self.user_owner, self.role_owner
)
self.user_contributor = self.make_user('user_contributor')
self.contributor_as = self.make_assignment(
Expand Down Expand Up @@ -2690,7 +2692,12 @@ def test_post(self):
RoleAssignment.objects.filter(project=self.project).count(), 0
)
self.assertEqual(self._get_delete_tl().count(), 1)
# self.assertEqual(self._get_delete_alerts().count(), 1)
alerts = self._get_delete_alerts()
self.assertEqual(alerts.count(), 3)
self.assertEqual(
sorted([a.user.username for a in alerts]),
sorted(['user_contributor', 'user_owner', 'user_owner_cat']),
)
'''
self.assertEqual(len(mail.outbox), 1)
self.assertIn(
Expand All @@ -2703,6 +2710,25 @@ def test_post(self):
)
'''

def test_post_as_owner(self):
"""Test POST as owner (should not receive alert or mail)"""
self.assertEqual(Project.objects.count(), 2)
self.assertEqual(self._get_delete_alerts().count(), 0)
self.assertEqual(len(mail.outbox), 0)

with self.login(self.user_owner):
response = self.client.post(self.url, {'status': True})

self.assertEqual(response.status_code, 302)
self.assertEqual(Project.objects.count(), 1)
alerts = self._get_delete_alerts()
self.assertEqual(alerts.count(), 2)
self.assertEqual(
sorted([a.user.username for a in alerts]),
sorted(['user_contributor', 'user_owner_cat']),
)
# TODO: Assert email


class TestProjectRoleView(ProjectMixin, RoleAssignmentMixin, ViewTestBase):
"""Tests for ProjectRoleView"""
Expand Down
105 changes: 69 additions & 36 deletions projectroles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@
'User can see nested {categories} and {projects}, but can not access them '
'without having a role explicitly assigned.'
)
PROJECT_DELETE_MSG = (
'{project_type} "{project_title}" deleted by user {user_name}.'
)
TARGET_CREATE_DISABLED_MSG = (
'PROJECTROLES_TARGET_CREATE=False, creation not allowed.'
)
Expand Down Expand Up @@ -1662,49 +1665,79 @@ def post(self, request, **kwargs):
class ProjectDeleteMixin(ProjectModifyPluginViewMixin):
"""Mixin for Project deletion in UI and API views"""

def _create_timeline_event(self, project, request):
"""
Create timeline summary event for project deletion. Created as a
classified site-wide event only viewable by superusers.
:param project: Project object
:param request: HttpRequest object
"""
timeline = get_backend_api('timeline_backend')
if not timeline:
return
local_users = {
a.user.username: a.role.name
for a in project.local_roles.order_by(
'role__rank', 'user__username'
)
}
parent = str(project.parent.sodar_uuid) if project.parent else None
extra_data = {
'title': project.title,
'type': project.type,
'parent': parent,
'description': project.description,
'readme': project.readme.raw,
'public_guest_access': project.public_guest_access,
'archive': project.archive,
'full_title': project.full_title,
'sodar_uuid': str(project.sodar_uuid),
'local_roles': local_users,
}
timeline.add_event(
project=None, # No project as it has been deleted
app_name=APP_NAME,
user=request.user,
event_name='project_delete',
description=f'delete {project.type.lower()} '
f'{project.get_log_title()}',
extra_data=extra_data,
classified=True,
status_type=timeline.TL_STATUS_OK,
)

def handle_delete(self, project, request):
"""
Handle project deletion.
Handle project deletion. Deletes the object, creates a summary timeline
event and sends out alerts and emails to project members.
:param project: Project object of project to be deleted
:param request: HttpRequest object
"""
timeline = get_backend_api('timeline_backend')
if timeline:
local_users = {
a.user.username: a.role.name
for a in project.local_roles.order_by(
'role__rank', 'user__username'
app_alerts = get_backend_api('appalerts_backend')
# Create timeline event
self._create_timeline_event(project, request)
# TODO: Check app_alerts and email setting here to avoid needless iter
roles = project.get_roles()
for role_as in roles:
user = role_as.user
# Skip alerting if same user
if user == request.user:
continue
if app_alerts:
app_alerts.add_alert(
app_name=APP_NAME,
alert_name='project_delete',
user=role_as.user,
message=PROJECT_DELETE_MSG.format(
project_type=get_display_name(project.type, title=True),
project_title=project.title,
user_name=request.user.username,
),
)
}
extra_data = {
'title': project.title,
'type': project.type,
'parent': (
str(project.parent.sodar_uuid) if project.parent else None
),
'description': project.description,
'readme': project.readme.raw,
'public_guest_access': project.public_guest_access,
'archive': project.archive,
'full_title': project.full_title,
'sodar_uuid': str(project.sodar_uuid),
'local_roles': local_users,
}
timeline.add_event(
project=None, # No project as it has been deleted
app_name=APP_NAME,
user=request.user,
event_name='project_delete',
description=f'delete {project.type.lower()} "{project.title}"',
extra_data=extra_data,
classified=True,
status_type=timeline.TL_STATUS_OK,
)
# TODO: Get project members, including inherited, omit request user
# TODO: Send alerts
# TODO: Send email
# Actually delete project
# TODO: Send email
# Actually delete project object
project.delete()


Expand Down

0 comments on commit dc56003

Please sign in to comment.