flows: export export/import functions in UI
This commit is contained in:
parent
b7ca40d98e
commit
a977184577
|
@ -0,0 +1,13 @@
|
|||
{% extends base_template|default:"generic/form.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block above_form %}
|
||||
<h1>
|
||||
{% trans 'Import Flow' %}
|
||||
</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block action %}
|
||||
{% trans 'Import Flow' %}
|
||||
{% endblock %}
|
|
@ -20,6 +20,7 @@
|
|||
<div class="pf-c-toolbar__content">
|
||||
<div class="pf-c-toolbar__bulk-select">
|
||||
<a href="{% url 'passbook_admin:flow-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
|
||||
<a href="{% url 'passbook_admin:flow-import' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-secondary" type="button">{% trans 'Import' %}</a>
|
||||
</div>
|
||||
{% include 'partials/pagination.html' %}
|
||||
</div>
|
||||
|
@ -62,6 +63,7 @@
|
|||
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:flow-update' pk=flow.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
|
||||
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:flow-delete' pk=flow.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
|
||||
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:flow-execute' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Execute' %}</a>
|
||||
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:flow-export' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Export' %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -81,6 +83,7 @@
|
|||
{% trans 'Currently no flows exist. Click the button below to create one.' %}
|
||||
</div>
|
||||
<a href="{% url 'passbook_admin:flow-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
|
||||
<a href="{% url 'passbook_admin:flow-import' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Import' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<div class="pf-l-stack__item">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<form action="" method="post" class="pf-c-form pf-m-horizontal">
|
||||
<form action="" method="post" class="pf-c-form pf-m-horizontal" enctype="multipart/form-data">
|
||||
{% include 'partials/form_horizontal.html' with form=form %}
|
||||
{% block beneath_form %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -191,6 +191,7 @@ urlpatterns = [
|
|||
# Flows
|
||||
path("flows/", flows.FlowListView.as_view(), name="flows"),
|
||||
path("flows/create/", flows.FlowCreateView.as_view(), name="flow-create",),
|
||||
path("flows/import/", flows.FlowImportView.as_view(), name="flow-import",),
|
||||
path(
|
||||
"flows/<uuid:pk>/update/", flows.FlowUpdateView.as_view(), name="flow-update",
|
||||
),
|
||||
|
@ -199,6 +200,9 @@ urlpatterns = [
|
|||
flows.FlowDebugExecuteView.as_view(),
|
||||
name="flow-execute",
|
||||
),
|
||||
path(
|
||||
"flows/<uuid:pk>/export/", flows.FlowExportView.as_view(), name="flow-export",
|
||||
),
|
||||
path(
|
||||
"flows/<uuid:pk>/delete/", flows.FlowDeleteView.as_view(), name="flow-delete",
|
||||
),
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
"""passbook Flow administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import DetailView, ListView, UpdateView
|
||||
from django.views.generic import DetailView, FormView, ListView, UpdateView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
|
||||
from passbook.admin.views.utils import DeleteMessageView
|
||||
from passbook.flows.forms import FlowForm
|
||||
from passbook.flows.forms import FlowForm, FlowImportForm
|
||||
from passbook.flows.models import Flow
|
||||
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||
from passbook.flows.transfer.exporter import FlowExporter
|
||||
from passbook.flows.transfer.importer import FlowImporter
|
||||
from passbook.flows.views import SESSION_KEY_PLAN, FlowPlanner
|
||||
from passbook.lib.utils.urls import redirect_with_qs
|
||||
from passbook.lib.views import CreateAssignPermView
|
||||
|
@ -88,3 +91,43 @@ class FlowDebugExecuteView(LoginRequiredMixin, PermissionRequiredMixin, DetailVi
|
|||
return redirect_with_qs(
|
||||
"passbook_flows:flow-executor-shell", self.request.GET, flow_slug=flow.slug,
|
||||
)
|
||||
|
||||
|
||||
class FlowImportView(LoginRequiredMixin, FormView):
|
||||
"""Import flow from JSON Export; only allowed for superusers
|
||||
as these flows can contain python code"""
|
||||
|
||||
form_class = FlowImportForm
|
||||
template_name = "administration/flow/import.html"
|
||||
success_url = reverse_lazy("passbook_admin:flows")
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_superuser:
|
||||
return self.handle_no_permission()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form: FlowImportForm) -> HttpResponse:
|
||||
importer = FlowImporter(form.cleaned_data["flow"].read().decode())
|
||||
successful = importer.apply()
|
||||
if not successful:
|
||||
messages.error(self.request, _("Failed to import flow."))
|
||||
else:
|
||||
messages.success(self.request, _("Successfully imported flow."))
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class FlowExportView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
"""Export Flow"""
|
||||
|
||||
model = Flow
|
||||
permission_required = "passbook_flows.export_flow"
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get(self, request: HttpRequest, pk: str) -> HttpResponse:
|
||||
"""Debug exectue flow, setting the current user as pending user"""
|
||||
flow: Flow = self.get_object()
|
||||
exporter = FlowExporter(flow)
|
||||
export = exporter.export_to_string()
|
||||
response = JsonResponse(export)
|
||||
response["Content-Disposition"] = f'attachment; filename="{flow.slug}.json"'
|
||||
return response
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
"""Flow and Stage forms"""
|
||||
|
||||
from django import forms
|
||||
from django.forms import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from passbook.flows.models import Flow, FlowStageBinding, Stage
|
||||
from passbook.flows.transfer.importer import FlowImporter
|
||||
from passbook.lib.widgets import GroupedModelChoiceField
|
||||
|
||||
|
||||
|
@ -53,3 +55,18 @@ class FlowStageBindingForm(forms.ModelForm):
|
|||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
}
|
||||
|
||||
|
||||
class FlowImportForm(forms.Form):
|
||||
"""Form used for flow importing"""
|
||||
|
||||
flow = forms.FileField()
|
||||
|
||||
def clean_flow(self):
|
||||
"""Check if the flow is valid and rewind the file to the start"""
|
||||
flow = self.cleaned_data["flow"].read()
|
||||
valid = FlowImporter(flow.decode()).validate()
|
||||
if not valid:
|
||||
raise ValidationError(_("Flow invalid."))
|
||||
self.cleaned_data["flow"].seek(0)
|
||||
return self.cleaned_data["flow"]
|
||||
|
|
|
@ -135,6 +135,10 @@ class Flow(SerializerModel, PolicyBindingModel):
|
|||
verbose_name = _("Flow")
|
||||
verbose_name_plural = _("Flows")
|
||||
|
||||
permissions = [
|
||||
("export_flow", "Can export a Flow"),
|
||||
]
|
||||
|
||||
|
||||
class FlowStageBinding(SerializerModel, PolicyBindingModel):
|
||||
"""Relationship between Flow and Stage. Order is required and unique for
|
||||
|
|
Reference in New Issue