[UPDT] HORILLA VIEWS: Update method name
This commit is contained in:
@@ -2,8 +2,10 @@
|
|||||||
horilla/cbv_methods.py
|
horilla/cbv_methods.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
import types
|
import types
|
||||||
import uuid
|
import uuid
|
||||||
|
from io import BytesIO
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
from venv import logger
|
from venv import logger
|
||||||
@@ -28,7 +30,10 @@ from django.urls import reverse
|
|||||||
from django.utils.functional import lazy
|
from django.utils.functional import lazy
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import SafeString
|
from django.utils.safestring import SafeString
|
||||||
from django.utils.translation import gettext_lazy as _trans
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from openpyxl import Workbook
|
||||||
|
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
|
||||||
|
from openpyxl.utils import get_column_letter
|
||||||
|
|
||||||
from horilla import settings
|
from horilla import settings
|
||||||
from horilla.horilla_middlewares import _thread_locals
|
from horilla.horilla_middlewares import _thread_locals
|
||||||
@@ -210,7 +215,7 @@ def check_feature_enabled(function, feature_name, model_class: models.Model):
|
|||||||
enabled = getattr(general_setting, feature_name, False)
|
enabled = getattr(general_setting, feature_name, False)
|
||||||
if enabled:
|
if enabled:
|
||||||
return function(self, request, *args, **kwargs)
|
return function(self, request, *args, **kwargs)
|
||||||
messages.info(request, _trans("Feature is not enabled on the settings"))
|
messages.info(request, _("Feature is not enabled on the settings"))
|
||||||
previous_url = request.META.get("HTTP_REFERER", "/")
|
previous_url = request.META.get("HTTP_REFERER", "/")
|
||||||
key = "HTTP_HX_REQUEST"
|
key = "HTTP_HX_REQUEST"
|
||||||
if key in request.META.keys():
|
if key in request.META.keys():
|
||||||
@@ -510,3 +515,147 @@ def merge_dicts(dict1, dict2):
|
|||||||
merged_dict[key] = value
|
merged_dict[key] = value
|
||||||
|
|
||||||
return merged_dict
|
return merged_dict
|
||||||
|
|
||||||
|
|
||||||
|
def flatten_dict(d, parent_key=""):
|
||||||
|
"""Recursively flattens a nested dictionary"""
|
||||||
|
items = []
|
||||||
|
for k, v in d.items():
|
||||||
|
new_key = k
|
||||||
|
if isinstance(v, dict):
|
||||||
|
items.extend(flatten_dict(v, new_key).items())
|
||||||
|
else:
|
||||||
|
items.append((new_key, v))
|
||||||
|
return dict(items)
|
||||||
|
|
||||||
|
|
||||||
|
def export_xlsx(json_data, columns):
|
||||||
|
"""
|
||||||
|
Quick export method
|
||||||
|
"""
|
||||||
|
top_fields = [col[0] for col in columns if len(col) == 2]
|
||||||
|
|
||||||
|
nested_fields = [
|
||||||
|
col for col in columns if len(col) == 3 and isinstance(col[2], dict)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Discover dynamic keys for each nested column
|
||||||
|
dynamic_columns = {}
|
||||||
|
for title, key, mappings in nested_fields:
|
||||||
|
dyn_keys = set()
|
||||||
|
for entry in json_data:
|
||||||
|
try:
|
||||||
|
nested_data = json.loads(entry.get(key, "[]").replace("'", '"'))
|
||||||
|
for item in nested_data:
|
||||||
|
flat = flatten_dict(item)
|
||||||
|
dyn_keys.update(flat.keys())
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
dynamic_columns[key] = {
|
||||||
|
"title": title,
|
||||||
|
"keys": [k for k in mappings if k in dyn_keys],
|
||||||
|
"display_names": mappings,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create workbook
|
||||||
|
wb = Workbook()
|
||||||
|
ws = wb.active
|
||||||
|
ws.title = "Quick Export"
|
||||||
|
|
||||||
|
# Header row
|
||||||
|
header = top_fields[:]
|
||||||
|
for nested_info in dynamic_columns.values():
|
||||||
|
for dyn_key in nested_info["keys"]:
|
||||||
|
display_name = nested_info["display_names"].get(dyn_key, dyn_key)
|
||||||
|
header.append(display_name)
|
||||||
|
ws.append(list(str(title) for title in header))
|
||||||
|
|
||||||
|
# Style definitions
|
||||||
|
header_fill = PatternFill(
|
||||||
|
start_color="FFD700", end_color="FFD700", fill_type="solid"
|
||||||
|
)
|
||||||
|
bold_font = Font(bold=True)
|
||||||
|
thin_border = Border(
|
||||||
|
left=Side(style="thin"),
|
||||||
|
right=Side(style="thin"),
|
||||||
|
top=Side(style="thin"),
|
||||||
|
bottom=Side(style="thin"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply styles to header
|
||||||
|
for col_idx, title in enumerate(header, 1):
|
||||||
|
cell = ws.cell(row=1, column=col_idx)
|
||||||
|
cell.font = bold_font
|
||||||
|
cell.fill = header_fill
|
||||||
|
cell.border = thin_border
|
||||||
|
cell.alignment = Alignment(horizontal="center", vertical="center")
|
||||||
|
|
||||||
|
row_index = 2
|
||||||
|
|
||||||
|
for entry in json_data:
|
||||||
|
all_nested_records = []
|
||||||
|
max_nested_rows = 1
|
||||||
|
|
||||||
|
for key, nested_info in dynamic_columns.items():
|
||||||
|
try:
|
||||||
|
nested_data = json.loads(entry.get(key, "[]").replace("'", '"'))
|
||||||
|
if not isinstance(nested_data, list):
|
||||||
|
nested_data = []
|
||||||
|
except Exception:
|
||||||
|
nested_data = []
|
||||||
|
all_nested_records.append(nested_data)
|
||||||
|
max_nested_rows = max(max_nested_rows, len(nested_data))
|
||||||
|
|
||||||
|
for i in range(max_nested_rows):
|
||||||
|
row = []
|
||||||
|
|
||||||
|
# Top fields
|
||||||
|
for tf in top_fields:
|
||||||
|
row.append(entry.get(tf, "") if i == 0 else "")
|
||||||
|
|
||||||
|
# Nested fields
|
||||||
|
for idx, (key, nested_info) in enumerate(dynamic_columns.items()):
|
||||||
|
nested_data = all_nested_records[idx]
|
||||||
|
flat_ans = flatten_dict(nested_data[i]) if i < len(nested_data) else {}
|
||||||
|
for dyn_key in nested_info["keys"]:
|
||||||
|
row.append(flat_ans.get(dyn_key, ""))
|
||||||
|
|
||||||
|
ws.append(row)
|
||||||
|
|
||||||
|
# Apply border to row
|
||||||
|
for col_idx in range(1, len(row) + 1):
|
||||||
|
cell = ws.cell(row=row_index, column=col_idx)
|
||||||
|
cell.border = thin_border
|
||||||
|
|
||||||
|
row_index += 1
|
||||||
|
|
||||||
|
# Merge top fields if needed
|
||||||
|
if max_nested_rows > 1:
|
||||||
|
for col_idx in range(1, len(top_fields) + 1):
|
||||||
|
ws.merge_cells(
|
||||||
|
start_row=row_index - max_nested_rows,
|
||||||
|
start_column=col_idx,
|
||||||
|
end_row=row_index - 1,
|
||||||
|
end_column=col_idx,
|
||||||
|
)
|
||||||
|
top_cell = ws.cell(row=row_index - max_nested_rows, column=col_idx)
|
||||||
|
top_cell.alignment = Alignment(vertical="center")
|
||||||
|
top_cell.border = thin_border # Re-apply border
|
||||||
|
|
||||||
|
# Auto-fit column widths
|
||||||
|
for col in ws.columns:
|
||||||
|
max_len = max(len(str(cell.value or "")) for cell in col)
|
||||||
|
col_letter = get_column_letter(col[0].column)
|
||||||
|
ws.column_dimensions[col_letter].width = min(max_len + 2, 50)
|
||||||
|
|
||||||
|
# Output file
|
||||||
|
output = BytesIO()
|
||||||
|
wb.save(output)
|
||||||
|
output.seek(0)
|
||||||
|
|
||||||
|
response = HttpResponse(
|
||||||
|
output.read(),
|
||||||
|
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
)
|
||||||
|
response["Content-Disposition"] = 'attachment; filename="quick_export.xlsx"'
|
||||||
|
return response
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from horilla.group_by import group_by_queryset
|
|||||||
from horilla.horilla_middlewares import _thread_locals
|
from horilla.horilla_middlewares import _thread_locals
|
||||||
from horilla_views import models
|
from horilla_views import models
|
||||||
from horilla_views.cbv_methods import ( # update_initial_cache,
|
from horilla_views.cbv_methods import ( # update_initial_cache,
|
||||||
|
export_xlsx,
|
||||||
get_short_uuid,
|
get_short_uuid,
|
||||||
hx_request_required,
|
hx_request_required,
|
||||||
paginator_qry,
|
paginator_qry,
|
||||||
@@ -488,12 +489,12 @@ class HorillaListView(ListView):
|
|||||||
return instance.pk
|
return instance.pk
|
||||||
|
|
||||||
for field_tuple in _columns:
|
for field_tuple in _columns:
|
||||||
dynamic_fn_str = f"def dehydrate_{field_tuple[1]}(self, instance):return self.remove_extra_spaces(getattribute(instance, '{field_tuple[1]}'))"
|
dynamic_fn_str = f"def dehydrate_{field_tuple[1]}(self, instance):return self.remove_extra_spaces(getattribute(instance, '{field_tuple[1]}'),{field_tuple})"
|
||||||
exec(dynamic_fn_str)
|
exec(dynamic_fn_str)
|
||||||
dynamic_fn = locals()[f"dehydrate_{field_tuple[1]}"]
|
dynamic_fn = locals()[f"dehydrate_{field_tuple[1]}"]
|
||||||
locals()[field_tuple[1]] = fields.Field(column_name=field_tuple[0])
|
locals()[field_tuple[1]] = fields.Field(column_name=field_tuple[0])
|
||||||
|
|
||||||
def remove_extra_spaces(self, text):
|
def remove_extra_spaces(self, text, field_tuple):
|
||||||
"""
|
"""
|
||||||
Remove blank space but keep line breaks and add new lines for <li> tags.
|
Remove blank space but keep line breaks and add new lines for <li> tags.
|
||||||
"""
|
"""
|
||||||
@@ -512,15 +513,46 @@ class HorillaListView(ListView):
|
|||||||
# Export the data using the resource
|
# Export the data using the resource
|
||||||
dataset = book_resource.export(queryset)
|
dataset = book_resource.export(queryset)
|
||||||
|
|
||||||
excel_data = dataset.export("xls")
|
# excel_data = dataset.export("xls")
|
||||||
|
|
||||||
# Set the response headers
|
# Set the response headers
|
||||||
file_name = self.export_file_name
|
# file_name = self.export_file_name
|
||||||
if not file_name:
|
# if not file_name:
|
||||||
file_name = "quick_export"
|
# file_name = "quick_export"
|
||||||
response = HttpResponse(excel_data, content_type="application/vnd.ms-excel")
|
# response = HttpResponse(excel_data, content_type="application/vnd.ms-excel")
|
||||||
response["Content-Disposition"] = f'attachment; filename="{file_name}.xls"'
|
# response["Content-Disposition"] = f'attachment; filename="{file_name}.xls"'
|
||||||
return response
|
# return response
|
||||||
|
json_data = json.loads(dataset.export("json"))
|
||||||
|
merged = [
|
||||||
|
(
|
||||||
|
[
|
||||||
|
*item,
|
||||||
|
next(
|
||||||
|
(
|
||||||
|
m
|
||||||
|
for (t, k, m) in self.export_fields
|
||||||
|
if t == item[0] and k == item[1]
|
||||||
|
),
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
if len(item) == 2
|
||||||
|
and any(
|
||||||
|
t == item[0] and k == item[1] for (t, k, _) in self.export_fields
|
||||||
|
)
|
||||||
|
else item
|
||||||
|
)
|
||||||
|
for item in _columns
|
||||||
|
]
|
||||||
|
columns = []
|
||||||
|
for column in merged:
|
||||||
|
if len(column) >= 3 and isinstance(column[2], dict):
|
||||||
|
column = (column[0], column[0], column[2])
|
||||||
|
elif len(column) >= 3:
|
||||||
|
column = (column[0], column[1])
|
||||||
|
columns.append(column)
|
||||||
|
|
||||||
|
return export_xlsx(json_data, columns)
|
||||||
|
|
||||||
|
|
||||||
class HorillaSectionView(TemplateView):
|
class HorillaSectionView(TemplateView):
|
||||||
|
|||||||
Reference in New Issue
Block a user