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
	 Jens Langhammer
					Jens Langhammer