[UPDT] HORILLA VIEWS: Update method name

This commit is contained in:
Horilla
2025-04-15 13:13:25 +05:30
parent 5fa43a44fc
commit 5a3ac788c6
2 changed files with 192 additions and 11 deletions

View File

@@ -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

View File

@@ -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):