Files
encoach_backend_new_v3/odoo/addons/web/controllers/pivot.py
Yamen Ahmad 3e83d8d7d5 feat: add complete EnCoach backend — Odoo 19 + all addons
Includes:
- Odoo 19 framework (odoo/)
- 27 custom EnCoach addons (new_project/custom_addons/)
  - encoach_core, encoach_api, encoach_lms_api, encoach_adaptive_api
  - encoach_exam, encoach_taxonomy, encoach_adaptive, encoach_assignment
  - encoach_ai, encoach_ai_grading, encoach_ai_generation, encoach_ai_media
  - encoach_courseware, encoach_communication, encoach_subscription
  - encoach_notification, encoach_approval, encoach_branding
  - encoach_classroom, encoach_registration, encoach_stats
  - encoach_faq, encoach_ticket, encoach_training, encoach_resources
  - encoach_adaptive_ai, encoach_sis
- 21 OpenEduCat Enterprise modules (new_project/enterprise-19/)
- 14 OpenEduCat Community modules (new_project/openeducat_erp-19.0/)
- Configuration: odoo.conf, requirements.txt, scripts
- 200+ REST API endpoints with JWT authentication
- SRS and test documentation

Made-with: Cursor
2026-04-01 17:10:04 +04:00

98 lines
4.1 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import io
import json
from collections import deque
from werkzeug.datastructures import FileStorage
from werkzeug.exceptions import UnprocessableEntity
from odoo import http, _
from odoo.http import content_disposition, request
from odoo.tools import osutil
class TableExporter(http.Controller):
@http.route('/web/pivot/export_xlsx', type='http', auth="user", readonly=True)
def export_xlsx(self, data, **kw):
import xlsxwriter # noqa: PLC0415
jdata = json.load(data) if isinstance(data, FileStorage) else json.loads(data)
if not jdata:
raise UnprocessableEntity(_('No data to export'))
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
worksheet = workbook.add_worksheet(jdata['title'])
header_bold = workbook.add_format({'bold': True, 'pattern': 1, 'bg_color': '#AAAAAA'})
header_plain = workbook.add_format({'pattern': 1, 'bg_color': '#AAAAAA'})
bold = workbook.add_format({'bold': True})
measure_count = min(jdata['measure_count'], 100000)
# Step 1: writing col group headers
col_group_headers = jdata['col_group_headers']
# x,y: current coordinates
# carry: queue containing cell information when a cell has a >= 2 height
# and the drawing code needs to add empty cells below
x, y, carry = 1, 0, deque()
for i, header_row in enumerate(col_group_headers):
worksheet.write(i, 0, '', header_plain)
for header in header_row:
while (carry and carry[0]['x'] == x):
cell = carry.popleft()
for j in range(measure_count):
worksheet.write(y, x+j, '', header_plain)
if cell['height'] > 1:
carry.append({'x': x, 'height': cell['height'] - 1})
x = x + measure_count
width = min(header['width'], 100000)
for j in range(width):
worksheet.write(y, x + j, header['title'] if j == 0 else '', header_plain)
if header['height'] > 1:
carry.append({'x': x, 'height': header['height'] - 1})
x = x + width
while (carry and carry[0]['x'] == x):
cell = carry.popleft()
for j in range(measure_count):
worksheet.write(y, x+j, '', header_plain)
if cell['height'] > 1:
carry.append({'x': x, 'height': cell['height'] - 1})
x = x + measure_count
x, y = 1, y + 1
# Step 2: writing measure headers
measure_headers = jdata['measure_headers']
if measure_headers:
worksheet.write(y, 0, '', header_plain)
for measure in measure_headers:
style = header_bold if measure['is_bold'] else header_plain
worksheet.write(y, x, measure['title'], style)
x = x + 1
x, y = 1, y + 1
# set minimum width of cells to 16 which is around 88px
worksheet.set_column(0, len(measure_headers), 16)
# Step 4: writing data
x = 0
for row in jdata['rows']:
worksheet.write(y, x, f"{row['indent'] * ' '}{row['title']}", header_plain)
for cell in row['values']:
x = x + 1
if cell.get('is_bold', False):
worksheet.write(y, x, cell['value'], bold)
else:
worksheet.write(y, x, cell['value'])
x, y = 0, y + 1
workbook.close()
xlsx_data = output.getvalue()
filename = osutil.clean_filename(_("Pivot %(title)s (%(model_name)s)", title=jdata['title'], model_name=jdata['model']))
response = request.make_response(xlsx_data,
headers=[('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'),
('Content-Disposition', content_disposition(filename + '.xlsx'))],
)
return response