@Lopus

Как загрузить файл в django с кириллическим названием?

Django отказывается загружать файлы на русском языке.
Ошибка: 'ascii' codec can't encode characters in position 48-52: ordinal not in range(128)
Поле любое (FileField или ImageField), django 1.9.4, python 2.7
Как можно решить эту проблему?
  • Вопрос задан
  • 1427 просмотров
Пригласить эксперта
Ответы на вопрос 4
Lertmind
@Lertmind
Проблема вероятно не с Django. Если погуглите, то у кого-то проблема с Nginx stackoverflow.com/a/7602446
Здесь ещё вопрос stackoverflow.com/questions/2457087/unicodedecodee..., в комментарии под ответом указывается, что надо правильно Apache настраивать https://code.djangoproject.com/ticket/11030#comment:5
Так что укажите на чём вы запускали: ОС и сервер.
UPD: Здесь itekblog.com/ascii-codec-cant-encode-characters-in... подробные варианты ошибок и решений.
Ответ написан
Комментировать
возможно это не самый разумный способ, но для быстрого решения подойдет.
Смысл в переписывании родных-джанговских upload handlor'ов
в settings.py
FILE_UPLOAD_HANDLERS = (
    'название_аппликейшена.uploadhandler.MemoryFileUploadHandler',
    'название_аппликейшена.uploadhandler.TemporaryFileUploadHandler',
)

И соответственно uploadhandler.py
from io import BytesIO
import os
from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile
from django.core.files.uploadhandler import FileUploadHandler, StopFutureHandlers
from django.conf import settings
import pytils
import re


def translify(value):
    value = pytils.translit.translify(u"%s" % value)
    value = re.sub("[\W]", "_", value.strip())
    return value


def transliteration_file_name(file_name):
    name, ext = os.path.splitext(file_name)
    return '{0}{1}'.format(translify(name), ext)


class TemporaryFileUploadHandler(FileUploadHandler):
    """
    Upload handler that streams data into a temporary file.
    """

    def __init__(self, *args, **kwargs):
        super(TemporaryFileUploadHandler, self).__init__(*args, **kwargs)

    def new_file(self, field_name, file_name, content_type, content_length, charset=None, content_type_extra=None):
        file_name = transliteration_file_name(file_name)
        super(TemporaryFileUploadHandler, self).new_file(field_name, file_name, content_type,
                                                         content_length, charset, content_type_extra)
        self.file = TemporaryUploadedFile(self.file_name, self.content_type, 0, self.charset, self.content_type_extra)

    def receive_data_chunk(self, raw_data, start):
        self.file.write(raw_data)

    def file_complete(self, file_size):
        self.file.seek(0)
        self.file.size = file_size
        return self.file


class MemoryFileUploadHandler(FileUploadHandler):
    """
    File upload handler to stream uploads into memory (used for small files).
    """

    def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
        """
        Use the content_length to signal whether or not this handler should be in use.
        """
        # Check the content-length header to see if we should
        # If the post is too large, we cannot use the Memory handler.
        if content_length > settings.FILE_UPLOAD_MAX_MEMORY_SIZE:
            self.activated = False
        else:
            self.activated = True

    def new_file(self, field_name, file_name, content_type, content_length, charset=None, content_type_extra=None):
        file_name = transliteration_file_name(file_name)
        super(MemoryFileUploadHandler, self).new_file(field_name, file_name, content_type,
                                                      content_length, charset, content_type_extra)
        if self.activated:
            self.file = BytesIO()
            raise StopFutureHandlers()

    def receive_data_chunk(self, raw_data, start):
        """
        Add the data to the BytesIO file.
        """
        if self.activated:
            self.file.write(raw_data)
        else:
            return raw_data

    def file_complete(self, file_size):
        """
        Return a file object if we're activated.
        """
        if not self.activated:
            return

        self.file.seek(0)
        return InMemoryUploadedFile(
            file=self.file,
            field_name=self.field_name,
            name=self.file_name,
            content_type=self.content_type,
            size=file_size,
            charset=self.charset,
            content_type_extra=self.content_type_extra
        )
Ответ написан
Комментировать
Avrong
@Avrong
Переводить название файла транслитом в латиницу либо просто присваивать свое произвольное название (номер).
Ответ написан
Комментировать
@reb00ter
Django developer
хоть вопрос и старый, всё равно оставлю тут свой вариант ответа - мало ли кому пригодится
Объявляется вот такой класс в качестве хранилища файлов
import unicodedata2
import pytils
from django.core.files.storage import FileSystemStorage

class ASCIIFileSystemStorage(FileSystemStorage):
    """
    Для автоматической транслитерации всех загружаемых файлов
    """
    def get_valid_name(self, name):
        name_parts = name.split('.')
        name = unicodedata2.normalize('NFKD', pytils.translit.slugify(name_parts[0])).encode('ascii', 'ignore').decode('utf-8')
        name = '{}.{}'.format(name, name_parts[-1])
        return super(ASCIIFileSystemStorage, self).get_valid_name(name)

и указывается в settings в качестве DEFAULT_FILE_STORAGE

по поводу формирования имени можно конечно ещё пообсуждать на предмет наличия более чем одной точки в имени файла и сделать чуть более красиво, но в целом это уже решает основную проблему
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы