Django: Попытка записать строку непосредственно в FileField при поддержке Amazon S3 - PullRequest
0 голосов
/ 30 сентября 2019

Я пытаюсь записать строку прямо в Django FileField с помощью ContentFile.

. При этом я получаю воспроизводимое

TypeError: Unicode-objects must be encoded before hashing

Ошибка

при попытке сохранить содержимое этого файла в базе данных, которая прослеживается через s3boto3 lib.

Точный источник этой ошибки трудно выяснить.

Но давайте прямо сформулируем вопрос, в Python 3, на Django 2.2.x, как правильно взять csv-файл, созданный с помощью csv lib из Python, и сохранить его в Django FileField, поддерживаемый Amazon S3?

Этот вопрос и мой подход вдохновлены этой записью о SO Django - как создать файл и сохранить его в FileField модели? - однако, учитывая возрастответ, некоторые детали, относящиеся к более новым версиям Django, похоже, были опущеныТрудно сказать.

Пример кода, приводящего к ошибке, усеченной для обеспечения конфиденциальности и релевантности

def campaign_to_csv_string(campaign_id):
    csv_string = io.StringIO()

    campaign = Campaign.objects.get(pk=campaign_id)
    checklist = campaign.checklist

    completed_jobs = JobRecord.objects.filter(appointment__campaign=campaign)

    writer = csv.writer(csv_string)

    # A bunch of writing to the writer here

    # string looks good at this point

    return csv_string.getvalue()

вызывающей функции

    csv_string = campaign_to_csv_string(campaign_report.campaign.pk)

    campaign_report.last_run = datetime.datetime.now()

    campaign_report.report_file.save(str(campaign_report_pk) + '.report', ContentFile(csv_string))

    campaign_report.processing = False

    campaign_report.save()

Я предполагаю, что s3boto3 проблема с ContentFile, но отправленная мне отладочная информация не дает четкого пути вперед.

edit

Трассировка стека по запросу

TypeError: Unicode-objects must be encoded before hashing
  File "celery/app/trace.py", line 385, in trace_task
    R = retval = fun(*args, **kwargs)
  File "celery/app/trace.py", line 648, in __protected_call__
    return self.run(*args, **kwargs)
  File "main/tasks.py", line 94, in produce_basic_campaign_report
    campaign_report.report_file.save(str(campaign_report_pk) + '.report', csv_file)
  File "django/db/models/fields/files.py", line 87, in save
    self.name = self.storage.save(name, content, max_length=self.field.max_length)
  File "django/core/files/storage.py", line 52, in save
    return self._save(name, content)
  File "storages/backends/s3boto3.py", line 491, in _save
    self._save_content(obj, content, parameters=parameters)
  File "storages/backends/s3boto3.py", line 506, in _save_content
    obj.upload_fileobj(content, ExtraArgs=put_parameters)
  File "boto3/s3/inject.py", line 621, in object_upload_fileobj
    ExtraArgs=ExtraArgs, Callback=Callback, Config=Config)
  File "boto3/s3/inject.py", line 539, in upload_fileobj
    return future.result()
  File "s3transfer/futures.py", line 106, in result
    return self._coordinator.result()
  File "s3transfer/futures.py", line 265, in result
    raise self._exception
  File "s3transfer/tasks.py", line 126, in __call__
    return self._execute_main(kwargs)
  File "s3transfer/tasks.py", line 150, in _execute_main
    return_value = self._main(**kwargs)
  File "s3transfer/upload.py", line 692, in _main
    client.put_object(Bucket=bucket, Key=key, Body=body, **extra_args)
  File "botocore/client.py", line 357, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "botocore/client.py", line 642, in _make_api_call
    request_signer=self._request_signer, context=request_context)
  File "botocore/hooks.py", line 360, in emit_until_response
    return self._emitter.emit_until_response(aliased_event_name, **kwargs)
  File "botocore/hooks.py", line 243, in emit_until_response
    responses = self._emit(event_name, kwargs, stop_on_response=True)
  File "botocore/hooks.py", line 211, in _emit
    response = handler(**kwargs)
  File "botocore/handlers.py", line 212, in conditionally_calculate_md5
    calculate_md5(params, **kwargs)
  File "botocore/handlers.py", line 190, in calculate_md5
    binary_md5 = _calculate_md5_from_file(body)
  File "botocore/handlers.py", line 204, in _calculate_md5_from_file
    md5.update(chunk)

1 Ответ

1 голос
/ 01 октября 2019

Строка csv должна быть закодирована в байтах при создании экземпляра ContentFile

. Ошибка может быть воспроизведена следующим образом:

from django.core.files.base import ContentFile
from botocore.handlers import _calculate_md5_from_file

_calculate_md5_from_file(ContentFile('throws error'))
TypeError: Unicode-objects must be encoded before hashing.

content не внутренне преобразовано вбайт, если это не mz-тип gzip или явно сжатый. https://github.com/jschneier/django-storages/blob/1.7.2/storages/backends/s3boto.py#L417

_calculate_md5_from_file ожидает файл, содержащий байты, и это то же самое для базового метода put_object клиента boto3 s3.

Я предлагаю кодировать csv_string в байтах.

    campaign_report.report_file.save(
        str(campaign_report_pk) + '.report', 
        ContentFile(
            csv_string.encode()
        )
    )
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...