mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-03-18 07:24:00 +01:00
feat(import): more UI and endpoints
This commit is contained in:
@@ -55,4 +55,11 @@ class ImportRunFileUploadForm(forms.Form):
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.form_method = "post"
|
||||
self.helper.layout = Layout("file")
|
||||
self.helper.layout = Layout(
|
||||
"file",
|
||||
FormActions(
|
||||
NoClassSubmit(
|
||||
"submit", _("Import"), css_class="btn btn-outline-primary w-100"
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -491,7 +491,6 @@ class ImportService:
|
||||
|
||||
for row_number, row in enumerate(reader, start=1):
|
||||
self._process_row(row, row_number)
|
||||
self._increment_totals("processed", value=1)
|
||||
|
||||
def _validate_file_path(self, file_path: str) -> str:
|
||||
"""
|
||||
@@ -518,15 +517,14 @@ class ImportService:
|
||||
if self.settings.file_type == "csv":
|
||||
self._process_csv(file_path)
|
||||
|
||||
if self.import_run.processed_rows == self.import_run.total_rows:
|
||||
self._update_status("FINISHED")
|
||||
self._log(
|
||||
"info",
|
||||
f"Import completed successfully. "
|
||||
f"Successful: {self.import_run.successful_rows}, "
|
||||
f"Failed: {self.import_run.failed_rows}, "
|
||||
f"Skipped: {self.import_run.skipped_rows}",
|
||||
)
|
||||
self._update_status("FINISHED")
|
||||
self._log(
|
||||
"info",
|
||||
f"Import completed successfully. "
|
||||
f"Successful: {self.import_run.successful_rows}, "
|
||||
f"Failed: {self.import_run.failed_rows}, "
|
||||
f"Skipped: {self.import_run.skipped_rows}",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self._update_status("FAILED")
|
||||
|
||||
@@ -13,6 +13,11 @@ urlpatterns = [
|
||||
views.import_profile_list,
|
||||
name="import_profiles_list",
|
||||
),
|
||||
path(
|
||||
"import/profiles/<int:profile_id>/delete/",
|
||||
views.import_profile_delete,
|
||||
name="import_profile_delete",
|
||||
),
|
||||
path(
|
||||
"import/profiles/add/",
|
||||
views.import_profile_add,
|
||||
@@ -24,14 +29,19 @@ urlpatterns = [
|
||||
name="import_profile_edit",
|
||||
),
|
||||
path(
|
||||
"import/profiles/<int:profile_id>/runs/",
|
||||
views.import_run_add,
|
||||
name="import_profile_runs_index",
|
||||
"import/profiles/<int:profile_id>/runs/list/",
|
||||
views.import_runs_list,
|
||||
name="import_profile_runs_list",
|
||||
),
|
||||
path(
|
||||
"import/profiles/<int:profile_id>/runs/list/",
|
||||
views.import_run_add,
|
||||
name="import_profile_runs_list",
|
||||
"import/profiles/<int:profile_id>/runs/<int:run_id>/log/",
|
||||
views.import_run_log,
|
||||
name="import_run_log",
|
||||
),
|
||||
path(
|
||||
"import/profiles/<int:profile_id>/runs/<int:run_id>/delete/",
|
||||
views.import_run_delete,
|
||||
name="import_run_delete",
|
||||
),
|
||||
path(
|
||||
"import/profiles/<int:profile_id>/runs/add/",
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -107,11 +108,30 @@ def import_profile_edit(request, profile_id):
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def import_run_list(request, profile_id):
|
||||
@csrf_exempt
|
||||
@require_http_methods(["DELETE"])
|
||||
def import_profile_delete(request, profile_id):
|
||||
profile = ImportProfile.objects.get(id=profile_id)
|
||||
|
||||
runs = ImportRun.objects.filter(profile=profile).order_by("id")
|
||||
profile.delete()
|
||||
|
||||
messages.success(request, _("Import Profile deleted successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def import_runs_list(request, profile_id):
|
||||
profile = ImportProfile.objects.get(id=profile_id)
|
||||
|
||||
runs = ImportRun.objects.filter(profile=profile).order_by("-id")
|
||||
|
||||
return render(
|
||||
request,
|
||||
@@ -120,6 +140,19 @@ def import_run_list(request, profile_id):
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def import_run_log(request, profile_id, run_id):
|
||||
run = ImportRun.objects.get(profile__id=profile_id, id=run_id)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"import_app/fragments/runs/log.html",
|
||||
{"run": run},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
@@ -140,6 +173,8 @@ def import_run_add(request, profile_id):
|
||||
# Defer the procrastinate task
|
||||
process_import.defer(import_run_id=import_run.id, file_path=file_path)
|
||||
|
||||
messages.success(request, _("Import Run queued successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
@@ -154,3 +189,22 @@ def import_run_add(request, profile_id):
|
||||
"import_app/fragments/runs/add.html",
|
||||
{"form": form, "profile": profile},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@csrf_exempt
|
||||
@require_http_methods(["DELETE"])
|
||||
def import_run_delete(request, profile_id, run_id):
|
||||
run = ImportRun.objects.get(profile__id=profile_id, id=run_id)
|
||||
|
||||
run.delete()
|
||||
|
||||
messages.success(request, _("Run deleted successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
status=204,
|
||||
headers={
|
||||
"HX-Trigger": "updated",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -38,18 +38,32 @@
|
||||
hx-get="{% url 'import_profile_edit' profile_id=profile.id %}"
|
||||
hx-target="#generic-offcanvas">
|
||||
<i class="fa-solid fa-pencil fa-fw"></i></a>
|
||||
{# <a class="btn btn-secondary btn-sm text-danger"#}
|
||||
{# role="button"#}
|
||||
{# data-bs-toggle="tooltip"#}
|
||||
{# data-bs-title="{% translate "Delete" %}"#}
|
||||
{# hx-delete="{% url 'account_delete' pk=account.id %}"#}
|
||||
{# hx-trigger='confirmed'#}
|
||||
{# data-bypass-on-ctrl="true"#}
|
||||
{# data-title="{% translate "Are you sure?" %}"#}
|
||||
{# data-text="{% translate "You won't be able to revert this!" %}"#}
|
||||
{# data-confirm-text="{% translate "Yes, delete it!" %}"#}
|
||||
{# _="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>#}
|
||||
{# </div>#}
|
||||
<a class="btn btn-secondary btn-sm text-success"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Runs" %}"
|
||||
hx-get="{% url 'import_profile_runs_list' profile_id=profile.id %}"
|
||||
hx-target="#persistent-generic-offcanvas-left">
|
||||
<i class="fa-solid fa-person-running fa-fw"></i></a>
|
||||
<a class="btn btn-secondary btn-sm text-primary"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Import" %}"
|
||||
hx-get="{% url 'import_run_add' profile_id=profile.id %}"
|
||||
hx-target="#generic-offcanvas">
|
||||
<i class="fa-solid fa-file-import fa-fw"></i></a>
|
||||
<a class="btn btn-secondary btn-sm text-danger"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Delete" %}"
|
||||
hx-delete="{% url 'import_profile_delete' profile_id=profile.id %}"
|
||||
hx-trigger='confirmed'
|
||||
data-bypass-on-ctrl="true"
|
||||
data-title="{% translate "Are you sure?" %}"
|
||||
data-text="{% translate "You won't be able to revert this!" %}"
|
||||
data-confirm-text="{% translate "Yes, delete it!" %}"
|
||||
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="col">{{ profile.name }}</td>
|
||||
<td class="col">{{ profile.get_version_display }}</td>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}{% translate 'Import file' %}{% endblock %}
|
||||
{% block title %}{% translate 'Import file with profile' %} {{ profile.name }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<form hx-post="{% url 'import_run_add' profile_id=profile.id %}" hx-target="#generic-offcanvas" novalidate>
|
||||
<form hx-post="{% url 'import_run_add' profile_id=profile.id %}" hx-target="#generic-offcanvas" enctype="multipart/form-data" novalidate>
|
||||
{% crispy form %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -5,5 +5,116 @@
|
||||
{% block title %}{% translate 'Runs for ' %}{{ profile.name }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div hx-get="{% url "import_profile_runs_list" profile_id=profile.id %}"
|
||||
hx-trigger="updated from:window"
|
||||
hx-target="closest .offcanvas"
|
||||
class="show-loading"
|
||||
hx-swap="show:none scroll:none">
|
||||
{% if runs %}
|
||||
<div class="row row-cols-1 g-4">
|
||||
{% for run in runs %}
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header tw-text-sm {% if run.status == run.Status.QUEUED %}tw-text-white{% elif run.status == run.Status.PROCESSING %}text-warning{% elif run.status == run.Status.FINISHED %}text-success{% else %}text-danger{% endif %}">
|
||||
<span><i class="fa-solid {% if run.status == run.Status.QUEUED %}fa-hourglass-half{% elif run.status == run.Status.PROCESSING %}fa-spinner{% elif run.status == run.Status.FINISHED %}fa-check{% else %}fa-xmark{% endif %} fa-fw me-2"></i>{{ run.get_status_display }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fa-solid fa-hashtag me-1 tw-text-xs tw-text-gray-400"></i>{{ run.id }}<span class="tw-text-xs tw-text-gray-400 ms-1">({{ run.file_name }})</span></h5>
|
||||
<hr>
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 w-100 g-4">
|
||||
<div class="col">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium">
|
||||
{% trans 'Total Items' %}
|
||||
</div>
|
||||
<div class="tw-text-sm">
|
||||
{{ run.total_rows }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium">
|
||||
{% trans 'Processed Items' %}
|
||||
</div>
|
||||
<div class="tw-text-sm">
|
||||
{{ run.processed_rows }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium">
|
||||
{% trans 'Skipped Items' %}
|
||||
</div>
|
||||
<div class="tw-text-sm">
|
||||
{{ run.skipped_rows }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium">
|
||||
{% trans 'Failed Items' %}
|
||||
</div>
|
||||
<div class="tw-text-sm">
|
||||
{{ run.failed_rows }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-body-secondary tw-text-xs tw-font-medium">
|
||||
{% trans 'Successful Items' %}
|
||||
</div>
|
||||
<div class="tw-text-sm">
|
||||
{{ run.successful_rows }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-body-secondary">
|
||||
<a class="text-decoration-none text-info"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Logs" %}"
|
||||
hx-get="{% url 'import_run_log' profile_id=profile.id run_id=run.id %}"
|
||||
hx-target="#generic-offcanvas"><i class="fa-solid fa-file-lines"></i></a>
|
||||
<a class="text-decoration-none text-danger"
|
||||
role="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{% translate "Delete" %}"
|
||||
hx-delete="{% url 'import_run_delete' profile_id=profile.id run_id=run.id %}"
|
||||
hx-trigger='confirmed'
|
||||
data-bypass-on-ctrl="true"
|
||||
data-title="{% translate "Are you sure?" %}"
|
||||
data-text="{% translate "You won't be able to revert this! All imported items will be kept." %}"
|
||||
data-confirm-text="{% translate "Yes, delete it!" %}"
|
||||
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<c-msg.empty title="{% translate "No runs yet" %}"></c-msg.empty>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user