2 changed files with 122 additions and 0 deletions
@ -135,6 +135,7 @@ pyjwt |
|||||
cryptography>=41.0.0 |
cryptography>=41.0.0 |
||||
django-celery-beat==2.5.0 |
django-celery-beat==2.5.0 |
||||
yt-dlp>=2024.3.10 |
yt-dlp>=2024.3.10 |
||||
|
openpyxl==3.1.5 |
||||
|
|
||||
|
|
||||
https://yaghoubi:[email protected]/NewHorizon/django-limitless-dashboard.git/archive/master.zip |
https://yaghoubi:[email protected]/NewHorizon/django-limitless-dashboard.git/archive/master.zip |
||||
|
|||||
@ -0,0 +1,121 @@ |
|||||
|
import io |
||||
|
from datetime import datetime, date |
||||
|
from typing import List, Any |
||||
|
from django.http import HttpResponse |
||||
|
from django.utils import timezone |
||||
|
import openpyxl |
||||
|
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side |
||||
|
from openpyxl.utils import get_column_letter |
||||
|
|
||||
|
|
||||
|
def export_to_excel_response( |
||||
|
filename: str, |
||||
|
headers: List[str], |
||||
|
rows: List[List[Any]], |
||||
|
sheet_title: str = "Export" |
||||
|
) -> HttpResponse: |
||||
|
""" |
||||
|
Generates a beautifully formatted Excel (.xlsx) file and returns it as a Django HttpResponse. |
||||
|
|
||||
|
:param filename: Base name of the file (without extension or with .xlsx) |
||||
|
:param headers: List of column header names |
||||
|
:param rows: List of data rows (each row is a list of cell values) |
||||
|
:param sheet_title: Title for the worksheet |
||||
|
:return: HttpResponse with .xlsx content type and attachment disposition |
||||
|
""" |
||||
|
wb = openpyxl.Workbook() |
||||
|
ws = wb.active |
||||
|
ws.title = sheet_title[:31] # Excel sheet title max 31 chars |
||||
|
|
||||
|
# Enable gridlines |
||||
|
ws.views.sheetView[0].showGridLines = True |
||||
|
|
||||
|
# 1. Styles Definition |
||||
|
# Modern dark indigo / slate header style |
||||
|
header_fill = PatternFill(start_color="1E293B", end_color="1E293B", fill_type="solid") |
||||
|
header_font = Font(name="Calibri", size=11, bold=True, color="FFFFFF") |
||||
|
header_alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) |
||||
|
|
||||
|
data_font = Font(name="Calibri", size=10, color="0F172A") |
||||
|
data_alignment_left = Alignment(horizontal="left", vertical="center") |
||||
|
data_alignment_center = Alignment(horizontal="center", vertical="center") |
||||
|
|
||||
|
thin_border_side = Side(border_style="thin", color="E2E8F0") |
||||
|
border_style = Border( |
||||
|
left=thin_border_side, |
||||
|
right=thin_border_side, |
||||
|
top=thin_border_side, |
||||
|
bottom=thin_border_side |
||||
|
) |
||||
|
|
||||
|
zebra_fill = PatternFill(start_color="F8FAFC", end_color="F8FAFC", fill_type="solid") |
||||
|
|
||||
|
# 2. Write Headers |
||||
|
ws.append(headers) |
||||
|
header_row = ws[1] |
||||
|
ws.row_dimensions[1].height = 28 |
||||
|
|
||||
|
for col_idx, cell in enumerate(header_row, 1): |
||||
|
cell.font = header_font |
||||
|
cell.fill = header_fill |
||||
|
cell.alignment = header_alignment |
||||
|
cell.border = border_style |
||||
|
|
||||
|
# 3. Write Data Rows |
||||
|
for row_idx, row_data in enumerate(rows, start=2): |
||||
|
formatted_row = [] |
||||
|
for val in row_data: |
||||
|
if val is None: |
||||
|
formatted_row.append("") |
||||
|
elif isinstance(val, (datetime, date)): |
||||
|
if isinstance(val, datetime) and timezone.is_aware(val): |
||||
|
val = timezone.localtime(val) |
||||
|
formatted_row.append(val.strftime("%Y-%m-%d %H:%M")) |
||||
|
elif isinstance(val, bool): |
||||
|
formatted_row.append("Yes" if val else "No") |
||||
|
else: |
||||
|
formatted_row.append(str(val)) |
||||
|
|
||||
|
ws.append(formatted_row) |
||||
|
current_row = ws[row_idx] |
||||
|
ws.row_dimensions[row_idx].height = 22 |
||||
|
|
||||
|
is_even = (row_idx % 2 == 0) |
||||
|
for col_idx, cell in enumerate(current_row, 1): |
||||
|
cell.font = data_font |
||||
|
cell.border = border_style |
||||
|
if is_even: |
||||
|
cell.fill = zebra_fill |
||||
|
# Align IDs or numbers center, text left |
||||
|
val = row_data[col_idx - 1] if col_idx - 1 < len(row_data) else None |
||||
|
if isinstance(val, (int, float, date, datetime)) or (isinstance(val, str) and val.isdigit()): |
||||
|
cell.alignment = data_alignment_center |
||||
|
else: |
||||
|
cell.alignment = data_alignment_left |
||||
|
|
||||
|
# 4. Auto-fit column widths (with min 12 and max 50) |
||||
|
for col in ws.columns: |
||||
|
max_len = 0 |
||||
|
col_letter = get_column_letter(col[0].column) |
||||
|
for cell in col: |
||||
|
val_str = str(cell.value or "") |
||||
|
if len(val_str) > max_len: |
||||
|
max_len = len(val_str) |
||||
|
adjusted_width = min(max(max_len + 4, 12), 50) |
||||
|
ws.column_dimensions[col_letter].width = adjusted_width |
||||
|
|
||||
|
# 5. Output to buffer |
||||
|
output = io.BytesIO() |
||||
|
wb.save(output) |
||||
|
output.seek(0) |
||||
|
|
||||
|
# Ensure .xlsx extension in filename |
||||
|
clean_filename = filename if filename.endswith(".xlsx") else f"{filename}.xlsx" |
||||
|
|
||||
|
response = HttpResponse( |
||||
|
output.getvalue(), |
||||
|
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" |
||||
|
) |
||||
|
response["Content-Disposition"] = f'attachment; filename="{clean_filename}"' |
||||
|
response["Access-Control-Expose-Headers"] = "Content-Disposition" |
||||
|
return response |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue