Skip to content

Commit

Permalink
add project deletion (#1090)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Jan 23, 2025
1 parent ed935b5 commit 44c171c
Show file tree
Hide file tree
Showing 22 changed files with 1,630 additions and 227 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ Added
- ``PROJECTROLES_READ_ONLY_MSG`` setting (#24)
- ``SiteReadOnlySettingAjaxView`` Ajax view (#24)
- ``siteappsettings`` site app plugin (#1304)
- ``SiteAppSettingsFormView`` view (#1304)
- ``SODARAppSettingFormMixin`` form helper mixin (#1545)
- Old owner "remove role" option in ``RoleAssignmentOwnerTransferForm`` (#836)
- Project deletion (#1090)
- ``ProjectModifyPluginMixin.perform_project_delete()`` method (#1090)
- ``ProjectDestroyAPIView`` REST API view (#1090)

Changed
-------
Expand Down
Binary file modified docs/source/_static/app_projectroles/sodar_archive.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/source/_static/app_projectroles/sodar_project_update.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/source/app_projectroles_api_rest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ Projectroles REST API Views

.. autoclass:: ProjectUpdateAPIView

.. autoclass:: ProjectDestroyAPIView

.. autoclass:: RoleAssignmentCreateAPIView

.. autoclass:: RoleAssignmentUpdateAPIView
Expand Down Expand Up @@ -182,5 +184,7 @@ Projectroles REST API Version Changes
v1.1
----

- ``ProjectDestroyAPIView``
* Add view
- ``RoleAssignmentOwnerTransferAPIView``
* Allow empty value for ``old_owner_role``
51 changes: 42 additions & 9 deletions docs/source/app_projectroles_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ the general view of your site is split into the following elements:

.. figure:: _static/app_projectroles/sodar_home.png
:align: center
:scale: 50%
:scale: 55%

Home view

Expand Down Expand Up @@ -102,7 +102,7 @@ app-specific controls is usually displayed.

.. figure:: _static/app_projectroles/sodar_project_detail.png
:align: center
:scale: 50%
:scale: 60%

Project detail view

Expand Down Expand Up @@ -147,7 +147,7 @@ may be modified later.

.. figure:: _static/app_projectroles/sodar_category_create.png
:align: center
:scale: 50%
:scale: 60%

Category/project creation form

Expand Down Expand Up @@ -176,7 +176,7 @@ target category or superuser status.

.. figure:: _static/app_projectroles/sodar_project_update.png
:align: center
:scale: 50%
:scale: 60%

Category/project updating form

Expand Down Expand Up @@ -254,23 +254,56 @@ management command:
Project Archiving
-----------------

From the project update menu, it is possible to archive a project. This will set
In the project update view it is possible to archive a project. Clicking the
:guilabel:`Archive` button and confirming this action in the next view will set
data modification from project access to read-only. User roles can still be
granted, but contributors can no longer edit data in project apps.

The project update menu will still be available for owners and delegates for
The project update view will still be available for owners and delegates for
updating basic project metadata. Superusers will be able to edit project data
regardless of its archiving status.

To undo archiving, the project can be unarchived from the same button on top of
the project update form.
the project update view, now labeled :guilabel:`Unarchive`.

.. figure:: _static/app_projectroles/sodar_archive.png
:align: center
:scale: 65%

Archived project and unarchive button in project update view

Project Deletion
----------------

The option to permanently delete a project is also available for owners and
delegates in the project update view. The :guilabel:`Delete` button is found
next to the project archiving button.

Deletion is disabled for categories which have nested child categories or child
projects. In such cases, the children must be individually deleted first. This
is done deliberately to decrease the chance of accidental deletion of multiple
projects.

If remote access for a project has been granted on target sites, access needs to
be revoked before the project can be deleted. Similarly, a project on a target
site can only be deleted if its access has been revoked on the source site. For
more information, see :ref:`app_projectroles_usage_remote`.

.. figure:: _static/app_projectroles/sodar_project_delete.png
:align: center
:scale: 65%

Project deletion confirmation form

Clicking the :guilabel:`Delete` button will take you to a form for confirming
the deletion. You will have to write the host name of the SODAR Core site to
confirm the action.

.. danger::

Deletion will permanently wipe out all data associated with a category or
project. This operation can not be undone!


Member Management
=================
Expand All @@ -281,7 +314,7 @@ category (owner or delegate) or superuser status.

.. figure:: _static/app_projectroles/sodar_role_list.png
:align: center
:scale: 50%
:scale: 55%

Project member list view

Expand Down Expand Up @@ -424,7 +457,7 @@ based site.

.. figure:: _static/app_projectroles/sodar_remote_sites.png
:align: center
:scale: 50%
:scale: 60%

Remote site list in source mode

Expand Down
25 changes: 25 additions & 0 deletions docs/source/dev_project_app.rst
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,31 @@ additional actions to be taken.
the project archive status, your app logic should handle it instead.


.. _dev_project_app_delete:

Project Deletion
================

If your apps only save data in Django models containing a ``Project`` foreign
key with cascading deletion, no extra steps are needed to support project
deletion.

If your app contains project-specific data which is e.g. stored in an external
system or in ways which will not be cascade-deleted along with the Django
``Project`` model object, you need to implement project deletion in the project
modify API. To do this, inherit ``ProjectModifyPluginMixin`` in your app's
plugin and implement the ``perform_project_delete()`` method to clean up data.

Project deletion can not be undone, so there is no revert method available for
this action.

.. note::

While categories are not expected to store data, the project deletion API
method is called for the deletion of both categories or projects, in case
speficic logic is needed for both project types.


Removing a Project App
======================

Expand Down
10 changes: 10 additions & 0 deletions docs/source/major_changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ v1.1.0 (WIP)
Release Highlights
==================

- Add project deletion
- Add site read-only mode
- Add siteappsettings site app plugin
- Add removeroles management command
Expand Down Expand Up @@ -52,6 +53,14 @@ prohibit modifying all data on the site. Rules, logic and/or UI of your apps'
views may have to be changed to support this functionality. For more
information, see :ref:`dev_resources_read_only`.

Project Deletion
----------------

This release enables the deletion of categories and projects. See the
:ref:`project app development documentation <dev_project_app_delete>` for more
information on how to support this feature in your apps.


AppSettingAPI Definition Getter Return Data
-------------------------------------------

Expand All @@ -75,6 +84,7 @@ REST API View Changes
- Projectroles API (``vnd.bihealth.sodar-core.projectroles``)
* Current version: ``1.1`` (non-breaking changes)
* Allowed versions: ``1.0``, ``1.1``
* ``ProjectDestroyAPIView``: Add view
* ``RoleAssignmentOwnerTransferAPIView``: Allow empty value for
``old_owner_role``

Expand Down
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
60 changes: 59 additions & 1 deletion projectroles/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@


SUBJECT_PROJECT_UNARCHIVE = (
'{project_label_title} "{project}" unarchived ' 'by {user}'
'{project_label_title} "{project}" unarchived by {user}'
)

MESSAGE_PROJECT_UNARCHIVE = r'''
Expand All @@ -220,6 +220,17 @@
'''.lstrip()


# Project Deletion Template ----------------------------------------------------

SUBJECT_PROJECT_DELETE = '{project_label_title} "{project}" deleted by {user}'

MESSAGE_PROJECT_DELETE = r'''
{user} has deleted "{project}".
The {project_label} has been removed from the site and can no
longer be accessed.
'''.lstrip()


# Email composing helpers ------------------------------------------------------


Expand Down Expand Up @@ -738,6 +749,53 @@ def send_project_archive_mail(project, action, request):
return mail_count


def send_project_delete_mail(project, request):
"""
Send a notification email on project deletion.
:param project: Project object
:param request: HttpRequest object
:return: Amount of sent email (int)
"""
user = request.user
project_users = [
a.user
for a in project.get_roles()
if a.user != user
and app_settings.get(APP_NAME, 'notify_email_project', user=a.user)
]
project_users = list(set(project_users))
if not project_users:
return 0

mail_count = 0
subject = SUBJECT_PROJECT_DELETE
body = MESSAGE_PROJECT_DELETE
subject = SUBJECT_PREFIX + subject.format(
project_label_title=get_display_name(project.type, title=True),
project=project.title,
user=user.get_full_name(),
)
body_final = body.format(
project_label=get_display_name(project.type),
project=project.title,
user=user.get_full_name(),
)

for recipient in project_users:
message = get_email_header(
MESSAGE_HEADER.format(
recipient=recipient.get_full_name(), site_title=SITE_TITLE
)
)
message += body_final
if not settings.PROJECTROLES_EMAIL_SENDER_REPLY:
message += NO_REPLY_NOTE
message += get_email_footer(request)
mail_count += send_mail(subject, message, get_user_addr(user), request)
return mail_count


def send_generic_mail(
subject_body,
message_body,
Expand Down
20 changes: 12 additions & 8 deletions projectroles/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,7 @@ def revert_project_setting_update(
"""
pass

def perform_project_archive(
self,
project,
):
def perform_project_archive(self, project):
"""
Perform additional actions to finalize project archiving or unarchiving.
The state being applied can be derived from the project.archive attr.
Expand All @@ -236,10 +233,7 @@ def perform_project_archive(
"""
pass

def revert_project_archive(
self,
project,
):
def revert_project_archive(self, project):
"""
Revert project archiving or unarchiving if errors have occurred in other
apps. The state being originally set can be derived from the
Expand All @@ -249,6 +243,16 @@ def revert_project_archive(
"""
pass

def perform_project_delete(self, project):
"""
Perform additional actions to finalize project deletion.
NOTE: This operation can not be undone so there is no revert method.
:param project: Project object (Project)
"""
pass


# Plugin Points ----------------------------------------------------------------

Expand Down
6 changes: 6 additions & 0 deletions projectroles/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,12 @@ def is_site_writable():
is_project_update_user & is_site_writable,
)

# Allow project deletion
rules.add_perm(
'projectroles.delete_project',
is_project_update_user & is_site_writable,
)

# Allow creation of projects
rules.add_perm(
'projectroles.create_project', is_project_create_user & can_create_projects
Expand Down
46 changes: 46 additions & 0 deletions projectroles/templates/projectroles/project_confirm_delete.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{% extends 'projectroles/project_base.html' %}

{% load rules %}
{% load projectroles_tags %}
{% load projectroles_common_tags %}

{% block title %}
Confirm Deletion of {% get_display_name object.type True %}
{% endblock title %}

{% block projectroles_extend %}

<div class="container-fluid sodar-page-container">
<h3>Confirm Deletion of {% get_display_name object.type True %}</h3>

<div class="alert alert-danger" role="alert">
Are you sure you want to delete this {% get_display_name object.type %}?
{% if object.type == 'PROJECT' %}
This will also delete all data within the project.
{% endif %}
This action can <strong>NOT</strong> be undone!
</div>

{# TODO: Add host name verification here #}

<form method="post">
{% csrf_token %}
<input class="form-control mb-3"
name="delete_host_confirm"
autocomplete="off"
required="true"
placeholder="Type the host name of this server (e.g. &quot;site.example.com&quot;) to confirm deletion." />
<div class="btn-group pull-right">
<a role="button" class="btn btn-secondary"
href="{% url 'projectroles:update' project=object.sodar_uuid %}">
<i class="iconify" data-icon="mdi:arrow-left-circle"></i> Cancel
</a>
<button type="submit" class="btn btn-danger sodar-btn-submit-once"
id="sodar-pr-btn-confirm-delete">
<i class="iconify" data-icon="mdi:close-thick"></i> Delete
</button>
</div>
</form>
</div>

{% endblock projectroles_extend %}
Loading

0 comments on commit 44c171c

Please sign in to comment.