[IMP] web: Clean the export file names
The purpose of this task is to clean up and provide clear export file names. Currently, When exporting data: - From a pivot view, the file name is 'table' - From a list view, the file name is technical name of model so in this commit, Change the export file name as below: - For list view quick export and standard record export, the filename will be 'model_description(model_technical_name)' - For pivot view quick export, the file name will be 'Pivot(string set on view)(model_technical_name)' and if string is not set then 'PivotUntitled(model_technical_name)' In all the cases space and forward slash will be removed. closes odoo/odoo#64123 Taskid: 2237840 Related: odoo/enterprise#15579 Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
This commit is contained in:
committed by
Xavier Morel
parent
6595e770f0
commit
892e3d7d25
@@ -34,7 +34,7 @@ import odoo
|
||||
import odoo.modules.registry
|
||||
from odoo.api import call_kw, Environment
|
||||
from odoo.modules import get_module_path, get_resource_path
|
||||
from odoo.tools import image_process, topological_sort, html_escape, pycompat, ustr, apply_inheritance_specs, lazy_property, float_repr
|
||||
from odoo.tools import image_process, topological_sort, html_escape, pycompat, ustr, apply_inheritance_specs, lazy_property, float_repr, osutil
|
||||
from odoo.tools.mimetypes import guess_mimetype
|
||||
from odoo.tools.translate import _
|
||||
from odoo.tools.misc import str2bool, xlsxwriter, file_open
|
||||
@@ -1884,12 +1884,20 @@ class ExportFormat(object):
|
||||
""" Provides the format's content type """
|
||||
raise NotImplementedError()
|
||||
|
||||
def filename(self, base):
|
||||
""" Creates a valid filename for the format (with extension) from the
|
||||
provided base name (exension-less)
|
||||
"""
|
||||
@property
|
||||
def extension(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def filename(self, base):
|
||||
""" Creates a filename *without extension* for the item / format of
|
||||
model ``base``.
|
||||
"""
|
||||
if base not in request.env:
|
||||
return base
|
||||
|
||||
model_description = request.env['ir.model']._get(base).name
|
||||
return f"{model_description} ({base})"
|
||||
|
||||
def from_data(self, fields, rows):
|
||||
""" Conversion method from Odoo's export data to whatever the
|
||||
current export class outputs
|
||||
@@ -1939,9 +1947,11 @@ class ExportFormat(object):
|
||||
export_data = records.export_data(field_names).get('datas',[])
|
||||
response_data = self.from_data(columns_headers, export_data)
|
||||
|
||||
# TODO: call `clean_filename` directly in `content_disposition`?
|
||||
return request.make_response(response_data,
|
||||
headers=[('Content-Disposition',
|
||||
content_disposition(self.filename(model))),
|
||||
content_disposition(
|
||||
osutil.clean_filename(self.filename(model) + self.extension))),
|
||||
('Content-Type', self.content_type)],
|
||||
cookies={'fileToken': token})
|
||||
|
||||
@@ -1956,8 +1966,9 @@ class CSVExport(ExportFormat, http.Controller):
|
||||
def content_type(self):
|
||||
return 'text/csv;charset=utf8'
|
||||
|
||||
def filename(self, base):
|
||||
return base + '.csv'
|
||||
@property
|
||||
def extension(self):
|
||||
return '.csv'
|
||||
|
||||
def from_group_data(self, fields, groups):
|
||||
raise UserError(_("Exporting grouped data to csv is not supported."))
|
||||
@@ -1991,8 +2002,9 @@ class ExcelExport(ExportFormat, http.Controller):
|
||||
def content_type(self):
|
||||
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
|
||||
def filename(self, base):
|
||||
return base + '.xlsx'
|
||||
@property
|
||||
def extension(self):
|
||||
return '.xlsx'
|
||||
|
||||
def from_group_data(self, fields, groups):
|
||||
with GroupExportXlsxWriter(fields, groups.count) as xlsx_writer:
|
||||
|
||||
@@ -5,9 +5,9 @@ from collections import deque
|
||||
import io
|
||||
import json
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.tools import ustr
|
||||
from odoo import http, _
|
||||
from odoo.http import content_disposition, request
|
||||
from odoo.tools import ustr, osutil
|
||||
from odoo.tools.misc import xlsxwriter
|
||||
|
||||
|
||||
@@ -102,9 +102,10 @@ class TableExporter(http.Controller):
|
||||
|
||||
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', 'attachment; filename=table.xlsx')],
|
||||
('Content-Disposition', content_disposition(filename + '.xlsx'))],
|
||||
cookies={'fileToken': token})
|
||||
|
||||
return response
|
||||
|
||||
@@ -140,6 +140,7 @@ odoo.define('web.PivotController', function (require) {
|
||||
}
|
||||
const table = this.model.exportData();
|
||||
table.title = this.title;
|
||||
table.model = this.modelName;
|
||||
session.get_file({
|
||||
url: '/web/pivot/export_xlsx',
|
||||
data: { data: JSON.stringify(table) },
|
||||
|
||||
@@ -5,7 +5,6 @@ from . import _monkeypatches
|
||||
from . import pycompat
|
||||
from . import win32
|
||||
from . import appdirs
|
||||
from . import osutil
|
||||
from . import pdf
|
||||
from . import cloc
|
||||
from .config import config
|
||||
@@ -21,3 +20,4 @@ from .xml_utils import *
|
||||
from .date_utils import *
|
||||
from .convert import *
|
||||
from .template_inheritance import *
|
||||
from . import osutil
|
||||
|
||||
+44
-2
@@ -4,15 +4,57 @@
|
||||
"""
|
||||
Some functions related to the os and os.path module
|
||||
"""
|
||||
from contextlib import contextmanager
|
||||
import logging
|
||||
import os
|
||||
from os.path import join as opj
|
||||
import re
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
from contextlib import contextmanager
|
||||
from os.path import join as opj
|
||||
|
||||
from .translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
WINDOWS_RESERVED = re.compile(r'''
|
||||
^
|
||||
# forbidden stems: reserved keywords
|
||||
(:?CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])
|
||||
# even with an extension this is recommended against
|
||||
(:?\..*)?
|
||||
$
|
||||
''', flags=re.IGNORECASE | re.VERBOSE)
|
||||
def clean_filename(name, replacement=''):
|
||||
""" Strips or replaces possibly problematic or annoying characters our of
|
||||
the input string, in order to make it a valid filename in most operating
|
||||
systems (including dropping reserved Windows filenames).
|
||||
|
||||
If this results in an empty string, results in "Untitled" (localized).
|
||||
|
||||
Allows:
|
||||
|
||||
* any alphanumeric character (unicode)
|
||||
* underscore (_) as that's innocuous
|
||||
* dot (.) except in leading position to avoid creating dotfiles
|
||||
* dash (-) except in leading position to avoid annoyance / confusion with
|
||||
command options
|
||||
* brackets ([ and ]), while they correspond to shell *character class*
|
||||
they're a common way to mark / tag files especially on windows
|
||||
* parenthesis ("(" and ")"), a more natural though less common version of
|
||||
the former
|
||||
* space (" ")
|
||||
|
||||
:param str name: file name to clean up
|
||||
:param str replacement:
|
||||
replacement string to use for sequences of problematic input, by default
|
||||
an empty string to remove them entirely, each contiguous sequence of
|
||||
problems is replaced by a single replacement
|
||||
:rtype: str
|
||||
"""
|
||||
if WINDOWS_RESERVED.match(name):
|
||||
return _("Untitled")
|
||||
return re.sub(r'[^\w_.()\[\] -]+', replacement, name).lstrip('.-') or _("Untitled")
|
||||
|
||||
def listdir(dir, recursive=False):
|
||||
"""Allow to recursively get the file listing following symlinks, returns
|
||||
|
||||
Reference in New Issue
Block a user