flows: export export/import functions in UI
This commit is contained in:
		
							
								
								
									
										13
									
								
								passbook/admin/templates/administration/flow/import.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								passbook/admin/templates/administration/flow/import.html
									
									
									
									
									
										Normal file
									
								
							@ -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
	
	Block a user