使用通用换行符处理Django的UploadedFile为UTF-8
在我的Django应用中,我提供了一个表单,让用户可以上传文件。这个文件可以有多种格式(比如Excel、CSV),可以来自不同的平台(Mac、Linux、Windows),而且编码方式也可能各不相同(如ASCII、UTF-8)。
为了方便讨论,假设我有一个视图,它接收了一个叫做request.FILES['file']
的文件,这个文件是InMemoryUploadedFile
的一个实例,称为file
。我的问题是,InMemoryUploadedFile
对象(像file
)有以下两个问题:
- 不支持UTF-8编码(我在文件开头看到一个
\xef\xbb\xbf
,据我了解,这是一个标记,表示“这个文件是UTF-8编码的”)。 - 不支持通用换行符(这可能是大多数上传到这个系统的文件所需要的)。
更复杂的是,我想把这个文件传给Python的csv
模块,而这个模块本身不支持Unicode。我很乐意接受那些能绕过这个问题的答案——一旦我让Django能正常处理UTF-8,我相信我可以强行让csv
也支持它。(同样,请忽略对Excel的支持要求——我会等CSV能正常工作后再处理Excel文件的解析。)
我尝试过使用StringIO
、mmap
、codec
,以及各种访问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 个回答
如果你想把CSV和Excel文件上传到Django,可以看看这个网站,它可能会对你有帮助。
我在使用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 )
如上所述,我提供的代码片段实际上是正常工作的,问题出在我的终端,而不是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。