2013-04-26 40 views
19

Tôi đang cố nhập tệp CSV, sử dụng biểu mẫu để tải tệp lên từ hệ thống khách. Sau khi tôi có tệp, tôi sẽ lấy một phần của nó và điền vào một mô hình trong ứng dụng của tôi. Tuy nhiên, tôi nhận được một "iterator nên trả lại chuỗi, không byte" lỗi khi tôi đi để lặp qua các dòng trong tập tin tải lên. Tôi đã dành hàng giờ cố gắng những thứ khác nhau và đọc tất cả mọi thứ tôi có thể tìm thấy trên này nhưng dường như không thể giải quyết nó (lưu ý, tôi là tương đối mới để Django-chạy 1,5 và python - chạy 3,3). Tôi đã loại bỏ mọi thứ để có được chỉ là lỗi và chạy nó như thế này để đảm bảo nó vẫn còn đó. Các lỗi được hiển thị khi thực hiện dòng "cho các câu lạc bộ trong club_list" trong tools_clubs_import():Cách giải quyết "trình vòng lặp phải trả lại chuỗi chứ không phải byte"

Sau đây là views.py chỉnh mà làm việc, dựa trên câu trả lời được đánh dấu dưới đây:

import csv 
from io import TextIOWrapper 
from django.shortcuts import render 
from django.http import HttpResponseRedirect 
from django.core.urlresolvers import reverse 
from rank.forms import ClubImportForm 

def tools_clubs_import(request): 
    if request.method == 'POST': 
     form = ClubImportForm(request.POST, request.FILES) 
     if form.is_valid(): 
      # the following 4 lines dumps request.META to a local file 
      # I saw a lot of questions about this so thought I'd post it too 
      log = open("/home/joel/meta.txt", "w") 
      for k, v in request.META.items(): 
       print ("%s: %s\n" % (k, request.META[k]), file=log) 
      log.close() 
      # I found I didn't need errors='replace', your mileage may vary 
      f = TextIOWrapper(request.FILES['filename'].file, 
        encoding='ASCII') 
      club_list = csv.DictReader(f) 
      for club in club_list: 
       # do something with each club dictionary entry 
       pass 
      return HttpResponseRedirect(reverse('rank.views.tools_clubs_import_show')) 
    else: 
     form = ClubImportForm() 

    context = {'form': form, 'active_menu_item': 4,} 
    return render(request, 'rank/tools_clubs_import.html', context) 

def tools_clubs_import_show(request): 
    return render(request, 'rank/tools_clubs_import_show.html') 

Các sau đây là phiên bản gốc của những gì tôi trình (html mà tạo ra các hình thức được bao gồm ở dưới cùng của danh sách mã này: Giá trị

views.py 
-------- 
import csv 
from django.shortcuts import render 
from django.http import HttpResponseRedirect 
from rank.forms import ClubImportForm 

def tools(request): 
    context = {'active_menu_item': 4,} 
    return render(request, 'rank/tools.html', context) 

def tools_clubs(request): 
    context = {'active_menu_item': 4,} 
    return render(request, 'rank/tools_clubs.html', context) 

def tools_clubs_import(request): 
    if request.method == 'POST': 
     form = ClubImportForm(request.POST, request.FILES) 
     if form.is_valid(): 
      f = request.FILES['filename'] 
      club_list = csv.DictReader(f) 
      for club in club_list: 
       # error occurs before anything here is executed 
       # process here... not included for brevity 
      return HttpResponseRedirect(reverse('rank.views.tools_clubs_import_show')) 
    else: 
     form = ClubImportForm() 

    context = {'form': form, 'active_menu_item': 4,} 
    return render(request, 'rank/tools_clubs_import.html', context) 

def tools_clubs_import_show(request): 
    return render(request, 'rank/tools_clubs_import_show.html') 

forms.py 
-------- 
from django import forms 


class ClubImportForm(forms.Form): 
    filename = forms.FileField(label='Select a CSV to import:',) 


urls.py 
------- 
from django.conf.urls import patterns, url 
from rank import views 

urlpatterns = patterns('', 
    url(r'^tools/$', views.tools, name='rank-tools'), 
    url(r'^tools/clubs/$', views.tools_clubs, name='rank-tools_clubs'), 
    url(r'^tools/clubs/import$', 
     views.tools_clubs_import, 
     name='rank-tools_clubs_import'), 
    url(r'^tools/clubs/import/show$', 
     views.tools_clubs_import_show, 
     name='rank-tools_clubs_import_show'), 
) 


tools_clubs_import.html 
----------------------- 
{% extends "rank/base.html" %} 
{% block title %}Tools/Club/Import{% endblock %} 
{% block center_col %} 

    <form enctype="multipart/form-data" method="post" action="{% url 'rank-tools_clubs_import' %}">{% csrf_token %} 
     {{ form.as_p }} 
     <input type="submit" value="Submit" /> 
    </form> 

{% endblock %} 

Ngoại lệ:

iterator nên trả lại chuỗi, không byte

Vị trí ngoại lệ (bạn đã mở tập tin trong chế độ văn bản?): /usr/lib/python3.3/csv.py trong fieldnames, dòng 96

Trả lời

28

request.FILES cung cấp cho bạn tệp nhị phân nhưng mô-đun csv muốn có tệp chế độ văn bản thay thế.

Bạn cần phải quấn tập tin trong một io.TextIOWrapper() instance, và bạn cần phải tìm ra các mã hóa:

from io import TextIOWrapper 

f = TextIOWrapper(request.FILES['filename'].file, encoding=request.encoding) 

Nó muốn có lẽ là tốt hơn nếu bạn lấy charset tham số từ Content-Type tiêu đề nếu được cung cấp; đó là những gì khách hàng nói với bạn bộ ký tự là.

Bạn không thể làm việc xung quanh cần biết mã hóa chính xác cho dữ liệu tệp; bạn có thể buộc giải thích như ASCII, ví dụ, bằng cách cung cấp một từ khóa errors cũng (cài đặt nó vào 'thay thế' hay 'bỏ qua'), nhưng điều đó không dẫn đến mất dữ liệu:

f = TextIOWrapper(request.FILES['filename'].file, encoding='ascii', errors='replace') 

Sử dụng TextIOWrapper sẽ chỉ làm việc khi sử dụng Django 1.11 trở lên (như this changeset added the required support). Trong các phiên bản trước đó, bạn có thể khỉ vá sự ủng hộ trong khi thực tế:

from django.core.files.utils import FileProxyMixin 

if not hasattr(FileProxyMixin, 'readable'): 
    # Pre-Django 1.11, add io.IOBase support, see 
    # https://github.com/django/django/commit/4f474607de9b470f977a734bdd47590ab202e778   
    def readable(self): 
     if self.closed: 
      return False 
     if hasattr(self.file, 'readable'): 
      return self.file.readable() 
     return True 

    def writable(self): 
     if self.closed: 
      return False 
     if hasattr(self.file, 'writable'): 
      return self.file.writable() 
     return 'w' in getattr(self.file, 'mode', '') 

    def seekable(self): 
     if self.closed: 
      return False 
     if hasattr(self.file, 'seekable'): 
      return self.file.seekable() 
     return True 

    FileProxyMixin.closed = property(
     lambda self: not self.file or self.file.closed) 
    FileProxyMixin.readable = readable 
    FileProxyMixin.writable = writable 
    FileProxyMixin.seekable = seekable 
+2

Đó tạo ra "đối tượng 'InMemoryUploadedFile' không có thuộc tính 'đọc'". – MeaOrdo

+1

@MeaOrdo: Đã cập nhật; xin lỗi, tôi không có một Django-on-3.x chạy atm, do đó, nó mã nguồn đọc và ngoại suy chỉ ở đây.:-) –

+0

Tôi đã thử một số biến thể về mã hóa với TextIOWrapper và sử dụng StringIO thay vì TextIOWrapper. Các mã hóa khác nhau trên TextIOWrapper cho phép Không thể giải mã tại lỗi byte 10 và tất cả các biến thể sử dụng StringIO cho TypeErrors và một số kết hợp tham chiếu đến "InMemoryUploadedFile". Có phải lỗi thực sự ở giai đoạn request.FILES ['filename']? Việc chuyển cho csv.DictReader không tạo ra lỗi cho đến khi bạn cố lặp lại kết quả. – MeaOrdo

8

trong python 3, tôi đã sử dụng:

import csv 
from io import StringIO 
csvf = StringIO(xls_file.read().decode()) 
reader = csv.reader(csvf, delimiter=',') 

xls_file là các tập tin nhận được từ các hình thức POST. Tôi hy vọng nó sẽ giúp.

0

Fuse hai phương pháp của bạn, điều này không bao giờ thất bại trong Python 3.5.2 và Django 1,9

delimitador = list_delimitadores[int(request.POST['delimitador'])][1] 
try: 
    text = TextIOWrapper(request.FILES['csv_x'].file, encoding='utf-8 ', errors='replace') 
    reader = csv.reader(text, delimiter=delimitador) 
except: 
    text = StringIO(request.FILES['csv_x'].file.read().decode()) 
    reader = csv.reader(text, delimiter=delimitador) 
Các vấn đề liên quan