한글을 포함한 CSV 파일을 깨지지 않게 다운로드하는 소스입니다.

끝에 uuid 쿠키를 설정하는 부분이 있는데, 이 쿠키는 다운로드 시작시 스피너를 표시하여 여러번 다운로드를 막고 다운로드가 완료되면 스피너를 제거하기 위해 추가했습니다.
다운로드 스피너와 관련된 웹 소스는 나중에 추가하도록 하겠습니다.

@bp_excel.route("/download_csv", methods=['GET'])
def download_csv():
    try:
        logger.debug("begin")

        uuid = request.args.get('uuid')
        if uuid is not None:
            uuid = uuid.strip()
        logger.debug("인자: uuid={}".format(uuid))

        logger.debug("CSV 파일 생성 시작")
        output = StringIO()
        # 한글 인코딩 위해 UTF-8 with BOM 설정해주기
        output.write(u'\ufeff')

        # 헤더
        output.write("날짜,순위,종목명\n")
        # 실제 사용시에는 쿼리 결과를 사용하지만, 테스트를 위해서 리스트 생성
        rows = [
            ['2019/05/06 10:14', 1, '네이버'],
            ['2019/05/06 10:14', 2, '삼성전자'],
            ['2019/05/06 10:14', 3, '셀트리온']
        ]
        for row in rows:
            # 컬럼 구분자 콤마(,)를 끝에는 추가하지 않도록 처리
            for index, col in enumerate(row):
                output.write(str(col))
                # 마지막 컬럼 전까지만 콤마(,) 추가
                if index < (len(row)-1):
                    output.write(',')
            output.write('\n')
        logger.debug("CSV 파일 생성 종료")

        logger.debug("Response 객체 생성 시작")
        # CSV 파일 형태로 브라우저가 파일다운로드라고 인식하도록 만들어주기
        response = Response(
            output.getvalue(),
            mimetype="text/csv",
            content_type='application/octet-stream'
        )
        # 다운받았을때의 파일 이름 지정
        filename = '20190506_juga'
        response.headers["Content-Disposition"] = "attachment; filename={}.csv".format(filename)

        # 브라우저에서 다운로드 완료를 체크할 수 있도록 쿠키 설정
        # 쿠키는 90초 이후에 사라지도록 설정
        response.set_cookie("uuid", uuid, max_age=90)
        logger.debug("Response 객체 생성 종료")

        return response
    except Exception as e:
        logger.error(traceback.format_exc())
    finally:
        logger.debug("end")