From 3be61a938c8df8fb7b82ed1dd550fa0a6088d911 Mon Sep 17 00:00:00 2001 From: Horilla Date: Thu, 16 Oct 2025 19:07:49 +0530 Subject: [PATCH] [UPDT] HORILLA_VIEWS: Report styling for company and title information --- horilla_views/cbv_methods.py | 98 ++++++++++++++++++++++++------------ horilla_views/views.py | 26 +++++++--- 2 files changed, 85 insertions(+), 39 deletions(-) diff --git a/horilla_views/cbv_methods.py b/horilla_views/cbv_methods.py index 299b05975..b639122e0 100644 --- a/horilla_views/cbv_methods.py +++ b/horilla_views/cbv_methods.py @@ -32,6 +32,7 @@ from django.utils.html import format_html from django.utils.safestring import SafeString from django.utils.translation import gettext_lazy as _ from openpyxl import Workbook +from openpyxl.drawing.image import Image from openpyxl.styles import Alignment, Border, Font, PatternFill, Side from openpyxl.utils import get_column_letter @@ -553,17 +554,21 @@ def flatten_dict(d, parent_key=""): return dict(items) -def export_xlsx(json_data, columns, file_name="quick_export"): +def export_xlsx(json_data, columns, file_name="quick_export", extra_info=None): """ - Quick export method + Quick export method with company info, logo, and date range header """ - top_fields = [col[0] for col in columns if len(col) == 2] + company_name = extra_info.get("company_name", "") if extra_info else "" + date_range = extra_info.get("date_range", "") if extra_info else "" + report_title = extra_info.get("report_title", "Export") if extra_info else "Export" + logo_path = extra_info.get("logo_path", "") if extra_info else "" # 👈 company logo + 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 + # --- Discover dynamic keys --- dynamic_columns = {} for title, key, mappings in nested_fields: dyn_keys = set() @@ -571,8 +576,7 @@ def export_xlsx(json_data, columns, file_name="quick_export"): try: nested_data = json.loads(entry.get(key, "[]").replace("'", '"')) for item in nested_data: - flat = flatten_dict(item) - dyn_keys.update(flat.keys()) + dyn_keys.update(item.keys()) except Exception: continue dynamic_columns[key] = { @@ -581,20 +585,61 @@ def export_xlsx(json_data, columns, file_name="quick_export"): "display_names": mappings, } - # Create workbook + # --- Workbook setup --- wb = Workbook() ws = wb.active ws.title = "Quick Export" - # Header row + total_columns = len(top_fields) + for nested_info in dynamic_columns.values(): + total_columns += len(nested_info["keys"]) + + # --- Styles --- + header_font_big = Font(size=14, bold=True) + title_font = Font(size=14, bold=True, color="FF0000") + center_align = Alignment(horizontal="center", vertical="center") + + # --- 1️⃣ Company Name Row --- + ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=total_columns) + company_cell = ws.cell(row=1, column=1) + company_cell.value = company_name + company_cell.font = header_font_big + company_cell.alignment = center_align + + # --- 2️⃣ Logo --- + if logo_path: + try: + logo = Image(logo_path) + logo.width = 120 + logo.height = 60 + ws.add_image(logo, "A1") # top-left corner + except Exception as e: + print(f"Logo load failed: {e}") + + # --- 3️⃣ Report Title (merged & centered) --- + ws.merge_cells(start_row=2, start_column=1, end_row=3, end_column=total_columns) + title_cell = ws.cell(row=2, column=1) + title_cell.value = report_title + title_cell.font = title_font + title_cell.alignment = center_align + + ws.merge_cells(start_row=4, start_column=1, end_row=4, end_column=total_columns) + date_cell = ws.cell(row=4, column=1) + date_cell.value = date_range + date_cell.alignment = center_align + + start_data_row = 6 + 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 + ws.append([]) + ws.append([str(title) for title in header]) + header_row_index = start_data_row + header_fill = PatternFill( start_color="FFD700", end_color="FFD700", fill_type="solid" ) @@ -606,16 +651,14 @@ def export_xlsx(json_data, columns, file_name="quick_export"): 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 = ws.cell(row=header_row_index, 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 + cell.alignment = center_align + row_index = header_row_index + 1 for entry in json_data: all_nested_records = [] max_nested_rows = 1 @@ -632,28 +675,20 @@ def export_xlsx(json_data, columns, file_name="quick_export"): 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 {} + nested_item = nested_data[i] if i < len(nested_data) else {} for dyn_key in nested_info["keys"]: - row.append(flat_ans.get(dyn_key, "")) - + row.append(nested_item.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 - + ws.cell(row=row_index, column=col_idx).border = thin_border row_index += 1 - # Merge top fields if needed + # Merge top-level fields when multiple nested rows exist if max_nested_rows > 1: for col_idx in range(1, len(top_fields) + 1): ws.merge_cells( @@ -662,21 +697,18 @@ def export_xlsx(json_data, columns, file_name="quick_export"): 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 + ws.cell(row=row_index - max_nested_rows, column=col_idx).alignment = ( + Alignment(vertical="center") + ) - # 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", diff --git a/horilla_views/views.py b/horilla_views/views.py index 77dd18097..7ba26bcfc 100644 --- a/horilla_views/views.py +++ b/horilla_views/views.py @@ -11,11 +11,6 @@ from django.contrib import messages from django.contrib.admin.utils import NestedObjects from django.core.cache import cache as CACHE from django.db import router -from django.db.models.fields.reverse_related import ( - ManyToManyRel, - ManyToOneRel, - OneToOneRel, -) from django.http import HttpResponse, HttpResponseForbidden, JsonResponse from django.shortcuts import render from django.utils.decorators import method_decorator @@ -1009,7 +1004,26 @@ def export_data(request, *args, **kwargs): f'attachment; filename="{export_file_name}.pdf"' ) return response - return export_xlsx(json_data, columns, file_name=export_file_name) + + logo_path = "" + company_title = "" + + company = request.selected_company_instance + if company: + logo_path = company.icon + company_title = company.company + + return export_xlsx( + json_data, + columns, + file_name=export_file_name, + extra_info={ + "company_name": company_title, + "date_range": request.session.get("report_date_range"), + "report_title": export_file_name, + "logo_path": logo_path, + }, + ) class DynamicView(View):