Подтвердить что ты не робот

Как разрешить "итератор должен возвращать строки, а не байты"

Я пытаюсь импортировать CSV файл, используя форму для загрузки файла из клиентской системы. После того, как у меня есть файл, я возьму его часть и запустил модель в своем приложении. Тем не менее, я получаю сообщение об ошибке "Итератор должен возвращать строки, а не байты", когда я перехожу к итерации по строкам в загруженном файле. Я потратил несколько часов на то, чтобы пробовать разные вещи и читать все, что я мог найти на этом, но не могу его решить (заметьте, я относительно новичок в Django 1.5 - и python - работает 3.3). Я убрал вещи, чтобы добраться до ошибки, и побежал так, чтобы убедиться, что он все еще там. Ошибка отображается при выполнении строки "для клубов в club_list" в tools_clubs_import():

Ниже приведено исправленное views.py, которое работает на основе ответа, отмеченного ниже:

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')

Ниже приведена исходная версия того, что я отправил (html, который генерирует форму, содержится в нижней части этого списка кодов:

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 %}

Значение исключения:

Итератор должен возвращать строки, а не байты (вы открыли файл в текстовом режиме?)

Место исключения:/usr/lib/python3.3/csv.py в именах полей, строка 96

4b9b3361

Ответ 1

request.FILES предоставляет двоичные файлы, но модуль csv хочет иметь файлы в текстовом режиме.

Вам нужно обернуть файл в io.TextIOWrapper() экземпляр, и вам нужно выяснить кодировку:

from io import TextIOWrapper

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

Вероятно, было бы лучше, если бы вы взяли параметр charset из заголовка Content-Type, если он указан; это то, что клиент сообщает вам набор символов.

Вы не можете обойтись, чтобы знать правильную кодировку данных файла; вы можете заставить интерпретацию как ASCII, например, предоставив ключевое слово errors (установив его на "replace" или "ignore" ), но это приводит к потере данных:

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

Использование TextIOWrapper будет работать только при использовании Django 1.11 и более поздних версий (в качестве этот набор изменений добавил необходимую поддержку). В более ранних версиях вы можете обезопасить поддержку после факта:

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

В python 3 я использовал:

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

xls_file - файл, полученный из формы POST. Надеюсь, это поможет.

Ответ 3

Предохраняйте ваши два метода, это никогда не сбой в Python 3.5.2 и 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)