[UPDT] HORILLA VIEWS: Export formats and general formats/styles updates
This commit is contained in:
@@ -529,7 +529,7 @@ def flatten_dict(d, parent_key=""):
|
|||||||
return dict(items)
|
return dict(items)
|
||||||
|
|
||||||
|
|
||||||
def export_xlsx(json_data, columns):
|
def export_xlsx(json_data, columns, file_name="quick_export"):
|
||||||
"""
|
"""
|
||||||
Quick export method
|
Quick export method
|
||||||
"""
|
"""
|
||||||
@@ -657,5 +657,5 @@ def export_xlsx(json_data, columns):
|
|||||||
output.read(),
|
output.read(),
|
||||||
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
)
|
)
|
||||||
response["Content-Disposition"] = 'attachment; filename="quick_export.xlsx"'
|
response["Content-Disposition"] = f'attachment; filename="{file_name}.xlsx"'
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
horilla/generic/views.py
|
horilla/generic/views.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -14,10 +15,12 @@ from django.core.cache import cache as CACHE
|
|||||||
from django.core.paginator import Page
|
from django.core.paginator import Page
|
||||||
from django.http import HttpRequest, HttpResponse, QueryDict
|
from django.http import HttpRequest, HttpResponse, QueryDict
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.template.loader import render_to_string
|
||||||
from django.urls import resolve, reverse
|
from django.urls import resolve, reverse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import DetailView, FormView, ListView, TemplateView
|
from django.views.generic import DetailView, FormView, ListView, TemplateView
|
||||||
|
from xhtml2pdf import pisa
|
||||||
|
|
||||||
from base.methods import closest_numbers, eval_validate, get_key_instances
|
from base.methods import closest_numbers, eval_validate, get_key_instances
|
||||||
from horilla.filters import FilterSet
|
from horilla.filters import FilterSet
|
||||||
@@ -49,7 +52,13 @@ class HorillaListView(ListView):
|
|||||||
|
|
||||||
view_id: str = """"""
|
view_id: str = """"""
|
||||||
|
|
||||||
export_file_name: str = None
|
export_file_name: str = "quick_export"
|
||||||
|
export_formats: list = [
|
||||||
|
("xlsx", "Excel"),
|
||||||
|
("json", "Json"),
|
||||||
|
("csv", "CSV"),
|
||||||
|
("pdf", "PDF"),
|
||||||
|
]
|
||||||
|
|
||||||
template_name: str = "generic/horilla_list_table.html"
|
template_name: str = "generic/horilla_list_table.html"
|
||||||
context_object_name = "queryset"
|
context_object_name = "queryset"
|
||||||
@@ -445,7 +454,7 @@ class HorillaListView(ListView):
|
|||||||
)
|
)
|
||||||
context["bulk_update_fields"] = self.bulk_update_fields
|
context["bulk_update_fields"] = self.bulk_update_fields
|
||||||
context["bulk_path"] = get_bulk_path
|
context["bulk_path"] = get_bulk_path
|
||||||
|
context["export_formats"] = self.export_formats
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def select_all(self, *args, **kwargs):
|
def select_all(self, *args, **kwargs):
|
||||||
@@ -463,6 +472,7 @@ class HorillaListView(ListView):
|
|||||||
request = getattr(_thread_locals, "request", None)
|
request = getattr(_thread_locals, "request", None)
|
||||||
ids = eval_validate(request.POST["ids"])
|
ids = eval_validate(request.POST["ids"])
|
||||||
_columns = eval_validate(request.POST["columns"])
|
_columns = eval_validate(request.POST["columns"])
|
||||||
|
export_format = request.POST.get("format", "xlsx")
|
||||||
queryset = self.model.objects.filter(id__in=ids)
|
queryset = self.model.objects.filter(id__in=ids)
|
||||||
|
|
||||||
_model = self.model
|
_model = self.model
|
||||||
@@ -589,6 +599,49 @@ class HorillaListView(ListView):
|
|||||||
column = (column[0], column[1])
|
column = (column[0], column[1])
|
||||||
columns.append(column)
|
columns.append(column)
|
||||||
|
|
||||||
|
if export_format == "json":
|
||||||
|
response = HttpResponse(
|
||||||
|
json.dumps(json_data, indent=4), content_type="application/json"
|
||||||
|
)
|
||||||
|
response["Content-Disposition"] = (
|
||||||
|
f'attachment; filename="{self.export_file_name}.json"'
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
# CSV
|
||||||
|
elif export_format == "csv":
|
||||||
|
csv_data = dataset.export("csv")
|
||||||
|
response = HttpResponse(csv_data, content_type="text/csv")
|
||||||
|
response["Content-Disposition"] = (
|
||||||
|
f'attachment; filename="{self.export_file_name}.csv"'
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
elif export_format == "pdf":
|
||||||
|
|
||||||
|
headers = dataset.headers
|
||||||
|
rows = dataset.dict
|
||||||
|
|
||||||
|
# Render to HTML using a template
|
||||||
|
html_string = render_to_string(
|
||||||
|
"generic/export_pdf.html",
|
||||||
|
{
|
||||||
|
"headers": headers,
|
||||||
|
"rows": rows,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert HTML to PDF using xhtml2pdf
|
||||||
|
result = io.BytesIO()
|
||||||
|
pisa_status = pisa.CreatePDF(html_string, dest=result)
|
||||||
|
|
||||||
|
if pisa_status.err:
|
||||||
|
return HttpResponse("PDF generation failed", status=500)
|
||||||
|
|
||||||
|
# Return response
|
||||||
|
response = HttpResponse(result.getvalue(), content_type="application/pdf")
|
||||||
|
response["Content-Disposition"] = (
|
||||||
|
f'attachment; filename="{self.export_file_name}.pdf"'
|
||||||
|
)
|
||||||
|
return response
|
||||||
return export_xlsx(json_data, columns)
|
return export_xlsx(json_data, columns)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -36,5 +36,5 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<input type="submit">
|
<input type="submit" hidden>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<div class="oh-modal" id="deleteConfirmation" role="dialog" aria-labelledby="deleteConfirmation" aria-hidden="true">
|
{% load i18n %}
|
||||||
|
<div style="z-index: 1022;" class="oh-modal" id="deleteConfirmation" role="dialog" aria-labelledby="deleteConfirmation" aria-hidden="true">
|
||||||
<div class="oh-modal__dialog oh-modal__dialog--custom" id="deleteConfirmationBody">
|
<div class="oh-modal__dialog oh-modal__dialog--custom" id="deleteConfirmationBody">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,6 +20,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="oh-modal" id="allocationModal" role="dialog" aria-labelledby="allocationModal" aria-hidden="true">
|
||||||
|
<div class="oh-modal__dialog oh-modal__dialog--custom oh-modal__dialog-process">
|
||||||
|
<div class="oh-modal__dialog-header">
|
||||||
|
<span class="oh-modal__dialog-title" id="addEmployeeModalLabel">{% trans "Allocation" %}</span>
|
||||||
|
<button class="oh-modal__close" aria-label="Close"><ion-icon name="close-outline"></ion-icon></button>
|
||||||
|
</div>
|
||||||
|
<div class="oh-modal__dialog-body oh-modal__dialog-body-process" id="allocationModalBody">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(document).on("htmx:afterOnLoad", function (event) {
|
$(document).on("htmx:afterOnLoad", function (event) {
|
||||||
$("[data-toggle='oh-modal-toggle']").click(function (e) {
|
$("[data-toggle='oh-modal-toggle']").click(function (e) {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
.check-list li:hover {
|
.check-list li:hover {
|
||||||
background-color: #e5ffff;
|
background-color: #e5ffff;
|
||||||
}
|
}
|
||||||
.oh-inner-sidebar__link{
|
.confirmation-sidebar--item{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
<a
|
<a
|
||||||
id="{{item.0.verbose_name|lower}}"
|
id="{{item.0.verbose_name|lower}}"
|
||||||
onclick="$(`[data-target='#{{key}}']`).click();$(this).parent().find('button').click()"
|
onclick="$(`[data-target='#{{key}}']`).click();$(this).parent().find('button').click()"
|
||||||
class="oh-inner-sidebar__link">{{item.0.verbose_name}}</a>
|
class="oh-inner-sidebar__link confirmation-sidebar--item">{{item.0.verbose_name}}</a>
|
||||||
<button
|
<button
|
||||||
hidden
|
hidden
|
||||||
hx-get="/{{dynamic_list_path|get_item:item.0.verbose_name}}"
|
hx-get="/{{dynamic_list_path|get_item:item.0.verbose_name}}"
|
||||||
@@ -214,7 +214,7 @@
|
|||||||
$(`#deleteConfirmationBody ${target}`).removeClass("d-none");
|
$(`#deleteConfirmationBody ${target}`).removeClass("d-none");
|
||||||
localStorage.setItem("deleteConfirmation",target)
|
localStorage.setItem("deleteConfirmation",target)
|
||||||
});
|
});
|
||||||
$(".oh-inner-sidebar__link").click(function (e) {
|
$(".confirmation-sidebar--item").click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$(this).closest("ul").find(".oh-inner-sidebar__link--active").removeClass("oh-inner-sidebar__link--active");
|
$(this).closest("ul").find(".oh-inner-sidebar__link--active").removeClass("oh-inner-sidebar__link--active");
|
||||||
$(this).addClass("oh-inner-sidebar__link--active");
|
$(this).addClass("oh-inner-sidebar__link--active");
|
||||||
|
|||||||
@@ -35,11 +35,18 @@
|
|||||||
<input type="hidden" name="columns" id="{{view_id}}ExportColumns">
|
<input type="hidden" name="columns" id="{{view_id}}ExportColumns">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||||
<div class="oh-input-group">
|
<div class="oh-input-group d-flex justify-content-between" style="align-items: center;">
|
||||||
<label class="oh-label" style="color:hsl(8deg 76.82% 46.12%);">
|
<label class="oh-label" style="color:hsl(8deg 76.82% 46.12%);">
|
||||||
<input type="checkbox" id="select-all-fields" /> {% trans "Select All Columns" %}
|
<input type="checkbox" id="select-all-export-fields" onchange="
|
||||||
|
$(this).closest('form').find('[type=checkbox]').prop('checked',$(this).is(':checked')).change();
|
||||||
|
" /> {% trans "Select All Columns" %}
|
||||||
</label>
|
</label>
|
||||||
|
<select name="format" style="height: 30px;">
|
||||||
|
{% for format in export_formats %}
|
||||||
|
<option value="{{format.0}}">{{format.1}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
65
horilla_views/templates/generic/export_pdf.html
Normal file
65
horilla_views/templates/generic/export_pdf.html
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
{% load horillafilters %}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid #333;
|
||||||
|
padding: 2px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Exported Data</h2>
|
||||||
|
<table style="width: 100%; border-collapse: collapse;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 30px;text-align: center;">SN</th>
|
||||||
|
{% for header in headers %}
|
||||||
|
{% if forloop.counter != 1 %}
|
||||||
|
<!-- Excluding the ID column -->
|
||||||
|
<th style="word-wrap: break-word; white-space: normal; padding: 2px;">{{ header }}</th>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in rows %}
|
||||||
|
<tr>
|
||||||
|
<td style="width: 30px;text-align: center;">{{ forloop.counter }}</td>
|
||||||
|
{% for header in headers %}
|
||||||
|
{% if forloop.counter != 1 %}
|
||||||
|
<!-- Excluding the ID column -->
|
||||||
|
<td style="word-wrap: break-word; white-space: normal; border: 1px solid #666; padding: 2px; max-width: 200px; overflow-wrap: break-word;">
|
||||||
|
{% with cell=row|get_item:header %}
|
||||||
|
{% if cell|length > 0 and not cell|stringformat:"s" == cell %}
|
||||||
|
<!-- cell is likely a list -->
|
||||||
|
<ul>
|
||||||
|
{% for item in cell %}
|
||||||
|
<li>{{ item }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<!-- cell is not a list -->
|
||||||
|
{{ cell }}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
{% if show_filter_tags %} {% include "generic/filter_tags.html" %} {% endif %}
|
{% if show_filter_tags %} {% include "generic/filter_tags.html" %} {% endif %}
|
||||||
<div class="oh-card">
|
<div class="oh-card">
|
||||||
{% if not groups.paginator.count %}
|
{% if not groups.paginator.count %}
|
||||||
|
{% if not custom_empty_template %}
|
||||||
<div class="oh-wrapper" align="center" style="margin-top: 7vh; margin-bottom:7vh;">
|
<div class="oh-wrapper" align="center" style="margin-top: 7vh; margin-bottom:7vh;">
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="{% static "images/ui/search.svg" %}" class="oh-404__image" alt="Page not found. 404.">
|
<img src="{% static "images/ui/search.svg" %}" class="oh-404__image" alt="Page not found. 404.">
|
||||||
@@ -33,6 +34,9 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% include custom_empty_template %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for group in groups %}
|
{% for group in groups %}
|
||||||
<div class="oh-accordion-meta">
|
<div class="oh-accordion-meta">
|
||||||
|
|||||||
@@ -32,9 +32,11 @@
|
|||||||
<a class="oh-timeoff-modal__profile-content" style="text-decoration: none"
|
<a class="oh-timeoff-modal__profile-content" style="text-decoration: none"
|
||||||
{{header.attrs|format:object|safe}}>
|
{{header.attrs|format:object|safe}}>
|
||||||
<div class="oh-profile mb-3">
|
<div class="oh-profile mb-3">
|
||||||
|
{% if header.avatar %}
|
||||||
<div class="oh-profile__avatar">
|
<div class="oh-profile__avatar">
|
||||||
<img src="{{object|getattribute:header.avatar}}" class="oh-profile__image me-2" />
|
<img src="{{object|getattribute:header.avatar}}" class="oh-profile__image me-2" />
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="oh-timeoff-modal__profile-info">
|
<div class="oh-timeoff-modal__profile-info">
|
||||||
<span class="oh-timeoff-modal__user fw-bold">
|
<span class="oh-timeoff-modal__user fw-bold">
|
||||||
{{object|getattribute:header.title|selected_format:request.user.employee_get.employee_work_info.company_id}}
|
{{object|getattribute:header.title|selected_format:request.user.employee_get.employee_work_info.company_id}}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{% load static i18n generic_template_filters %}
|
{% load static i18n generic_template_filters %}
|
||||||
<div id="{{ view_id|safe }}">
|
<div id="{{ view_id|safe }}" class="hlv-container">
|
||||||
<div
|
<div
|
||||||
class="oh-modal"
|
class="oh-modal"
|
||||||
id="bulkUpdateModal{{view_id|safe}}"
|
id="bulkUpdateModal{{view_id|safe}}"
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
{% load static i18n generic_template_filters %}
|
{% load static i18n generic_template_filters %}
|
||||||
<div class="oh-card mt-4 mb-5" id="{{view_id|safe}}">
|
<div class="oh-card mt-4 mb-5" id="{{view_id|safe}}">
|
||||||
|
<style>
|
||||||
|
.oh-general__tab-link--active{
|
||||||
|
padding-bottom: .2rem;
|
||||||
|
}
|
||||||
|
li.oh-general__tab{
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="selectedInstanceIds" data-ids="[]" ></div>
|
||||||
<button
|
<button
|
||||||
hidden
|
hidden
|
||||||
hx-get="{{request.path}}"
|
hx-get="{{request.path}}"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="oh-tabs__movable-area mb-2">
|
<div class="oh-tabs__movable-area mb-2">
|
||||||
<div class="oh-tabs__movable">
|
<div class="oh-tabs__movable">
|
||||||
<div class="oh-tabs__movable-header" onclick="$(this).parent().find('.oh-tabs__movable-body').toggleClass('d-none');">
|
<div class="oh-tabs__movable-header" onclick="$(this).parent().find('.oh-tabs__movable-body').toggleClass('d-none');">
|
||||||
<input class="oh-tabs__movable-title oh-table__editable-input" value="{{group.stage}}" readonly />
|
<input class="oh-tabs__movable-title oh-table__editable-input" value="{{group}}" readonly />
|
||||||
|
|
||||||
<div onclick="event.stopPropagation()" class="oh-dropdown" x-data="{open: false}">
|
<div onclick="event.stopPropagation()" class="oh-dropdown" x-data="{open: false}">
|
||||||
<div style="cursor: pointer;" onclick="event.stopPropagation()" class="oh-dropdown" x-data="{open: false}">
|
<div style="cursor: pointer;" onclick="event.stopPropagation()" class="oh-dropdown" x-data="{open: false}">
|
||||||
|
|||||||
Reference in New Issue
Block a user