使用通用换行符处理Django的UploadedFile为UTF-8

14 投票
3 回答
11363 浏览
提问于 2025-04-16 10:12

在我的Django应用中,我提供了一个表单,让用户可以上传文件。这个文件可以有多种格式(比如Excel、CSV),可以来自不同的平台(Mac、Linux、Windows),而且编码方式也可能各不相同(如ASCII、UTF-8)。

为了方便讨论,假设我有一个视图,它接收了一个叫做request.FILES['file']的文件,这个文件是InMemoryUploadedFile的一个实例,称为file。我的问题是,InMemoryUploadedFile对象(像file)有以下两个问题:

  1. 不支持UTF-8编码(我在文件开头看到一个\xef\xbb\xbf,据我了解,这是一个标记,表示“这个文件是UTF-8编码的”)。
  2. 不支持通用换行符(这可能是大多数上传到这个系统的文件所需要的)。

更复杂的是,我想把这个文件传给Python的csv模块,而这个模块本身不支持Unicode。我很乐意接受那些能绕过这个问题的答案——一旦我让Django能正常处理UTF-8,我相信我可以强行让csv也支持它。(同样,请忽略对Excel的支持要求——我会等CSV能正常工作后再处理Excel文件的解析。)

我尝试过使用StringIOmmapcodec,以及各种访问InMemoryUploadedFile对象数据的方法。每种方法都产生了不同的错误,目前为止没有一种是完美的。这是我觉得最接近的代码:

import csv
import codecs

class CSVParser:
    def __init__(self,file):
        # 'file' is assumed to be an InMemoryUploadedFile object.
        dialect = csv.Sniffer().sniff(codecs.EncodedFile(file,"utf-8").read(1024))
        file.open() # seek to 0
        self.reader = csv.reader(codecs.EncodedFile(file,"utf-8"),
                                 dialect=dialect)
        try:
            self.field_names = self.reader.next()
        except StopIteration:
            # The file was empty - this is not allowed.
            raise ValueError('Unrecognized format (empty file)')

        if len(self.field_names) <= 1:
            # This probably isn't a CSV file at all.
            # Note that the csv module will (incorrectly) parse ALL files, even
            # binary data. This will catch most such files.
            raise ValueError('Unrecognized format (too few columns)')

        # Additional methods snipped, unrelated to issue

请注意,我在实际的解析算法上没有花太多时间,所以可能效率很低,现在我更关心的是编码能否按预期工作。

问题是,尽管结果被包裹在Unicode的codecs.EncodedFile文件包装器中,但结果仍然没有被编码。

编辑:结果发现,上面的代码实际上是有效的。codecs.EncodedFile(file,"utf-8")就是解决办法。原来我以为它不工作的原因是我使用的终端不支持UTF-8。活到老,学到老!

3 个回答

-1

如果你想把CSV和Excel文件上传到Django,可以看看这个网站,它可能会对你有帮助。

3

我在使用csv.DictReader,感觉效果不错。我附上了我的代码片段,但其实和这里的另一个回答差不多。

import csv as csv_mod
import codecs

file = request.FILES['file']    
dialect = csv_mod.Sniffer().sniff(codecs.EncodedFile(file,"utf-8").read(1024))
file.open() 
csv = csv_mod.DictReader( codecs.EncodedFile(file,"utf-8"), dialect=dialect )
11

如上所述,我提供的代码片段实际上是正常工作的,问题出在我的终端,而不是Python的编码。

如果你的视图需要访问一个UTF-8格式的UploadedFile,你可以直接使用utf8_file = codecs.EncodedFile(request.FILES['file_field'],"utf-8")来以正确的编码打开文件对象。

我还注意到,至少对于InMemoryUploadedFile类型,通过codecs.EncodedFile这个包装器打开文件并不会重置文件描述符的seek()位置。为了返回到文件的开头(这可能是InMemoryUploadedFile特有的情况),我只是用了request.FILES['file_field'].open()来把seek()的位置重新设置为0。

撰写回答