mirror of
https://github.com/clearml/clearml-server
synced 2025-06-26 23:15:47 +00:00
Exported csv file name now contains the project name (including non-ascii names)
This commit is contained in:
parent
5c80336aa9
commit
3ad636c468
@ -4,4 +4,5 @@ tags_cache {
|
|||||||
download {
|
download {
|
||||||
redis_timeout_sec: 300
|
redis_timeout_sec: 300
|
||||||
batch_size: 500
|
batch_size: 500
|
||||||
|
max_project_name_length: 60
|
||||||
}
|
}
|
@ -1,8 +1,10 @@
|
|||||||
|
import unicodedata
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from flask import request, Response, redirect
|
from flask import request, Response, redirect
|
||||||
from werkzeug.datastructures import ImmutableMultiDict
|
from werkzeug.datastructures import ImmutableMultiDict
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
|
from werkzeug.urls import url_quote
|
||||||
|
|
||||||
from apiserver.apierrors import APIError
|
from apiserver.apierrors import APIError
|
||||||
from apiserver.apierrors.base import BaseError
|
from apiserver.apierrors.base import BaseError
|
||||||
@ -44,9 +46,17 @@ class RequestHandlers:
|
|||||||
else:
|
else:
|
||||||
headers = None
|
headers = None
|
||||||
if call.result.filename:
|
if call.result.filename:
|
||||||
headers = {
|
try:
|
||||||
"Content-Disposition": f"attachment; filename={call.result.filename}"
|
call.result.filename.encode("ascii")
|
||||||
}
|
except UnicodeEncodeError:
|
||||||
|
simple = unicodedata.normalize("NFKD", call.result.filename)
|
||||||
|
simple = simple.encode("ascii", "ignore").decode("ascii")
|
||||||
|
# safe = RFC 5987 attr-char
|
||||||
|
quoted = url_quote(call.result.filename, safe="")
|
||||||
|
filenames = f"filename={simple}; filename*=UTF-8''{quoted}"
|
||||||
|
else:
|
||||||
|
filenames = f"filename={call.result.filename}"
|
||||||
|
headers = {"Content-Disposition": "attachment; " + filenames}
|
||||||
|
|
||||||
response = Response(
|
response = Response(
|
||||||
content,
|
content,
|
||||||
|
@ -177,6 +177,9 @@ def _get_download_getter_fn(
|
|||||||
return getter
|
return getter
|
||||||
|
|
||||||
|
|
||||||
|
download_conf = config.get("services.organization.download")
|
||||||
|
|
||||||
|
|
||||||
@endpoint("organization.prepare_download_for_get_all")
|
@endpoint("organization.prepare_download_for_get_all")
|
||||||
def prepare_download_for_get_all(
|
def prepare_download_for_get_all(
|
||||||
call: APICall, company: str, request: PrepareDownloadForGetAll
|
call: APICall, company: str, request: PrepareDownloadForGetAll
|
||||||
@ -194,7 +197,7 @@ def prepare_download_for_get_all(
|
|||||||
|
|
||||||
redis.setex(
|
redis.setex(
|
||||||
f"get_all_download_{call.id}",
|
f"get_all_download_{call.id}",
|
||||||
int(config.get("services.organization.download.redis_timeout_sec", 300)),
|
int(download_conf.get("redis_timeout_sec", 300)),
|
||||||
json.dumps(call.data),
|
json.dumps(call.data),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -248,7 +251,7 @@ def download_for_get_all(call: APICall, company, request: DownloadForGetAll):
|
|||||||
with ThreadPoolExecutor(1) as pool:
|
with ThreadPoolExecutor(1) as pool:
|
||||||
page = 0
|
page = 0
|
||||||
page_size = int(
|
page_size = int(
|
||||||
config.get("services.organization.download.batch_size", 500)
|
download_conf.get("batch_size", 500)
|
||||||
)
|
)
|
||||||
future = pool.submit(get_fn, page, page_size)
|
future = pool.submit(get_fn, page, page_size)
|
||||||
|
|
||||||
@ -270,6 +273,26 @@ def download_for_get_all(call: APICall, company, request: DownloadForGetAll):
|
|||||||
if page == 0:
|
if page == 0:
|
||||||
yield csv.writer(SingleLine()).writerow(projection)
|
yield csv.writer(SingleLine()).writerow(projection)
|
||||||
|
|
||||||
call.result.filename = f"{request.entity_type}_export.csv"
|
def get_project_name() -> Optional[str]:
|
||||||
|
projects = call_data.get("project")
|
||||||
|
if not projects or not isinstance(projects, (list, str)):
|
||||||
|
return
|
||||||
|
if isinstance(projects, list):
|
||||||
|
if len(projects) > 1:
|
||||||
|
return
|
||||||
|
projects = projects[0]
|
||||||
|
if projects is None:
|
||||||
|
return "root"
|
||||||
|
project: Project = Project.objects(id=projects).only("basename").first()
|
||||||
|
if not project:
|
||||||
|
return
|
||||||
|
|
||||||
|
return project.basename[:download_conf.get("max_project_name_length", 60)]
|
||||||
|
|
||||||
|
call.result.filename = "-".join(
|
||||||
|
filter(
|
||||||
|
None, ("clearml", get_project_name(), f"{request.entity_type}s.csv")
|
||||||
|
)
|
||||||
|
)
|
||||||
call.result.content_type = "text/csv"
|
call.result.content_type = "text/csv"
|
||||||
call.result.raw_data = stream_with_context(generate())
|
call.result.raw_data = stream_with_context(generate())
|
||||||
|
Loading…
Reference in New Issue
Block a user