Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add snapshot exports and PROJECT_SNAPSHOT_EXPORTS to settings #1171

Merged
merged 2 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions rdmo/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
'PROJECT_ISSUES',
'PROJECT_VIEWS',
'PROJECT_EXPORTS',
'PROJECT_SNAPSHOT_EXPORTS',
'PROJECT_IMPORTS',
'PROJECT_IMPORTS_LIST',
'PROJECT_SEND_ISSUE',
Expand Down Expand Up @@ -294,6 +295,8 @@
('json', _('JSON'), 'rdmo.projects.exports.JSONExport'),
]

PROJECT_SNAPSHOT_EXPORTS = []

PROJECT_IMPORTS = [
('xml', _('RDMO XML'), 'rdmo.projects.imports.RDMOXMLImport'),
]
Expand Down
3 changes: 3 additions & 0 deletions rdmo/core/static/core/css/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,12 @@ a {

.btn-link {
color: $link-color;
padding: 0;
text-decoration: none;

&:hover {
color: $link-color-hover;
text-decoration: none;
}
}

Expand Down
14 changes: 11 additions & 3 deletions rdmo/projects/exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from rdmo.views.utils import ProjectWrapper

from .renderers import XMLRenderer
from .serializers.export import ProjectSerializer as ExportSerializer
from .serializers.export import ProjectSerializer as ProjectExportSerializer
from .serializers.export import SnapshotSerializer as SnapshotExportSerializer


class Export(Plugin):
Expand Down Expand Up @@ -155,11 +156,18 @@ def render(self):
class RDMOXMLExport(Export):

def render(self):
serializer = ExportSerializer(self.project)
if self.project:
content_disposition = f'attachment; filename="{self.project.title}.xml"'
serializer = ProjectExportSerializer(self.project)

else:
content_disposition = f'attachment; filename="{self.snapshot.title}.xml"'
serializer = SnapshotExportSerializer(self.snapshot)

xmldata = XMLRenderer().render(serializer.data)
response = HttpResponse(prettify_xml(xmldata), content_type="application/xml")

if settings.EXPORT_CONTENT_DISPOSITION == 'attachment':
response['Content-Disposition'] = f'attachment; filename="{self.project.title}.xml"'
response['Content-Disposition'] = content_disposition

return response
1 change: 1 addition & 0 deletions rdmo/projects/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def is_site_manager_for_current_site(user, request):
rules.add_perm('projects.add_snapshot_object', is_project_manager | is_project_owner | is_site_manager)
rules.add_perm('projects.change_snapshot_object', is_project_manager | is_project_owner | is_site_manager)
rules.add_perm('projects.rollback_snapshot_object', is_project_manager | is_project_owner | is_site_manager)
rules.add_perm('projects.export_snapshot_object', is_project_owner | is_project_manager | is_site_manager)

rules.add_perm('projects.view_value_object', is_project_member | is_site_manager)
rules.add_perm('projects.add_value_object', is_project_author | is_project_manager | is_project_owner | is_site_manager)
Expand Down
36 changes: 35 additions & 1 deletion rdmo/projects/serializers/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,40 @@ class SnapshotSerializer(serializers.ModelSerializer):

values = serializers.SerializerMethodField()

catalog = serializers.CharField(source='catalog.uri', default=None, read_only=True)
tasks = serializers.SerializerMethodField()
views = serializers.SerializerMethodField()

class Meta:
model = Snapshot
fields = (
'title',
'description',
'catalog',
'tasks',
'views',
'values',
'created',
'updated'
)

def get_values(self, obj):
values = Value.objects.filter(project=obj.project, snapshot=obj) \
.select_related('attribute', 'option')
serializer = ValueSerializer(instance=values, many=True)
return serializer.data

def get_tasks(self, obj):
return [task.uri for task in obj.project.tasks.all()]

def get_views(self, obj):
return [view.uri for view in obj.project.views.all()]


class ProjectSnapshotSerializer(serializers.ModelSerializer):

values = serializers.SerializerMethodField()

class Meta:
model = Snapshot
fields = (
Expand All @@ -57,7 +91,7 @@ def get_values(self, obj):

class ProjectSerializer(serializers.ModelSerializer):

snapshots = SnapshotSerializer(many=True)
snapshots = ProjectSnapshotSerializer(many=True)
values = serializers.SerializerMethodField()

catalog = serializers.CharField(source='catalog.uri', default=None, read_only=True)
Expand Down
18 changes: 18 additions & 0 deletions rdmo/projects/templates/projects/project_detail_snapshots.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@ <h2>{% trans 'Snapshots' %}</h2>
title="{% trans 'Rollback to snapshot' %}">
</a>
{% endif %}

{% has_perm 'projects.export_project_object' request.user project as can_export_project %}
{% if settings.PROJECT_SNAPSHOT_EXPORTS and can_export_project %}
<span class="dropdown">
<button class="btn-link fa fa-download" title="{% trans 'Export snapshot' %}"
data-toggle="dropdown"></button>

<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton">
{% for key, label, class in settings.PROJECT_SNAPSHOT_EXPORTS %}
<li>
<a href="{% url 'snapshot_export' project.id snapshot.id key %}" target="_blank">
{{ label }}
</a>
</li>
{% endfor %}
{% endif %}
</ul>
</span>
</td>
</tr>
{% endfor %}
Expand Down
30 changes: 30 additions & 0 deletions rdmo/projects/tests/test_view_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
'site': [1, 2, 3, 4, 5]
}

export_snapshot_permission_map = {
'owner': [1, 2, 3, 4, 5],
'manager': [1, 3, 5],
'api': [1, 2, 3, 4, 5],
'site': [1, 2, 3, 4, 5]
}

projects = [1, 2, 3, 4, 5]
snapshots = [1, 3, 7, 4, 5, 6]

Expand Down Expand Up @@ -206,3 +213,26 @@ def test_snapshot_rollback_post(db, client, files, username, password, project_i
assert response.status_code == 302
else:
assert response.status_code == 404


@pytest.mark.parametrize('username,password', users)
@pytest.mark.parametrize('project_id', projects)
@pytest.mark.parametrize('snapshot_id', snapshots)
def test_snapshot_export_xml(db, client, files, username, password, project_id, snapshot_id):
client.login(username=username, password=password)
project = Project.objects.get(pk=project_id)
project_snapshots = list(project.snapshots.values_list('id', flat=True))

url = reverse('snapshot_export', args=[project_id, snapshot_id, 'xml'])
response = client.get(url)

if snapshot_id in project_snapshots:
if project_id in export_snapshot_permission_map.get(username, []):
assert response.status_code == 200
else:
if password:
assert response.status_code == 403
else:
assert response.status_code == 302
else:
assert response.status_code == 404
3 changes: 3 additions & 0 deletions rdmo/projects/urls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
ProjectViewExportView,
ProjectViewView,
SnapshotCreateView,
SnapshotExportView,
SnapshotRollbackView,
SnapshotUpdateView,
)
Expand Down Expand Up @@ -109,6 +110,8 @@
SnapshotUpdateView.as_view(), name='snapshot_update'),
re_path(r'^(?P<project_id>[0-9]+)/snapshots/(?P<pk>[0-9]+)/rollback/$',
SnapshotRollbackView.as_view(), name='snapshot_rollback'),
re_path(r'^(?P<project_id>[0-9]+)/snapshots/(?P<pk>[0-9]+)/export/(?P<format>[a-z-]+)/$',
SnapshotExportView.as_view(), name='snapshot_export'),

re_path(r'^(?P<pk>[0-9]+)/answers/$',
ProjectAnswersView.as_view(), name='project_answers'),
Expand Down
2 changes: 1 addition & 1 deletion rdmo/projects/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@
ProjectUpdateViewsView,
)
from .project_view import ProjectViewExportView, ProjectViewView
from .snapshot import SnapshotCreateView, SnapshotRollbackView, SnapshotUpdateView
from .snapshot import SnapshotCreateView, SnapshotExportView, SnapshotRollbackView, SnapshotUpdateView
33 changes: 32 additions & 1 deletion rdmo/projects/views/snapshot.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import logging

from django.http import HttpResponseRedirect
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.views.generic import CreateView, DetailView, UpdateView

from rdmo.core.plugins import get_plugin
from rdmo.core.views import ObjectPermissionMixin, RedirectViewMixin

from ..forms import SnapshotCreateForm
Expand Down Expand Up @@ -63,3 +64,33 @@ def post(self, request, *args, **kwargs):
snapshot.rollback()

return HttpResponseRedirect(reverse('project', args=[snapshot.project.id]))


class SnapshotExportView(ObjectPermissionMixin, DetailView):
model = Snapshot
queryset = Snapshot.objects.all()
permission_required = 'projects.export_snapshot_object'

def get_queryset(self):
return Snapshot.objects.filter(project_id=self.kwargs['project_id'])

def get_permission_object(self):
return self.get_object().project

def get_export_plugin(self):
export_plugin = get_plugin('PROJECT_SNAPSHOT_EXPORTS', self.kwargs.get('format'))
if export_plugin is None:
raise Http404

export_plugin.request = self.request
export_plugin.snapshot = self.object

return export_plugin

def get(self, request, *args, **kwargs):
self.object = self.get_object()
return self.get_export_plugin().render()

def post(self, request, *args, **kwargs):
self.object = self.get_object()
return self.get_export_plugin().submit()
4 changes: 4 additions & 0 deletions testing/config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@

PROJECT_REMOVE_VIEWS = True

PROJECT_SNAPSHOT_EXPORTS = [
('xml', _('RDMO XML'), 'rdmo.projects.exports.RDMOXMLExport'),
]

EMAIL_RECIPIENTS_CHOICES = [
('[email protected]', 'Emmi Email <[email protected]>'),
]
Expand Down
Loading