it-swarm.com.de

So beheben Sie "Iterator sollte Zeichenfolgen zurückgeben, nicht Bytes"

Ich versuche, eine CSV-Datei mithilfe eines Formulars zu importieren, um die Datei vom Client-System hochzuladen. Nachdem ich die Datei habe, nehme ich Teile davon und fülle ein Modell in meiner App auf. Ich erhalte jedoch den Fehler "Iterator sollte Strings und nicht Bytes zurückgeben", wenn ich über die Zeilen in der hochgeladenen Datei iteriere. Ich habe stundenlang verschiedene Dinge ausprobiert und alles gelesen, was ich dazu finden konnte, aber es scheint, als ob ich es nicht lösen kann. Ich habe Dinge entfernt, um nur den Fehler zu finden, und habe es so ausgeführt, um sicherzustellen, dass es immer noch da ist. Der Fehler wird angezeigt, wenn in tools_clubs_import () die Zeile "für Clubs in club_list" ausgeführt wird:

Das Folgende ist die korrigierte views.py, die funktioniert, basierend auf der unten markierten Antwort:

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

Das Folgende ist die Originalversion dessen, was ich eingereicht habe (der HTML-Code, der das Formular generiert, befindet sich am Ende dieser Codeliste:

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

Ausnahmewert:

der Iterator sollte Zeichenfolgen und keine Bytes zurückgeben. (Haben Sie die Datei im Textmodus geöffnet?)

Ausnahmeposition: /usr/lib/python3.3/csv.py in Feldnamen, Zeile 96

23
MeaOrdo

request.FILES gibt Ihnen binary files, aber das Modul csv möchte stattdessen Dateien im Textmodus haben.

Sie müssen die Datei in ein io.TextIOWrapper() instance einschließen und die Codierung herausfinden:

from io import TextIOWrapper

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

Es wäre wahrscheinlich besser, wenn Sie den Parameter charset aus dem Header Content-Type nehmen würden, falls vorhanden. Das sagt Ihnen der Client, der Zeichensatz ist.

Sie können nicht umgehen, wenn Sie die richtige Codierung für die Dateidaten kennen müssen. Sie können die Interpretation als ASCII-Code erzwingen, indem Sie beispielsweise auch ein Schlüsselwort errors angeben ("Ersetzen" oder "Ignorieren"). Dies führt jedoch zu Datenverlust:

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

Die Verwendung von TextIOWrapper funktioniert nur mit Django 1.11 und höher (als dieser Änderungssatz fügte die erforderliche Unterstützung hinzu ). In früheren Versionen können Sie die Unterstützung nachträglich mit einem Affen-Patch versehen:

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
39
Martijn Pieters

In Python 3 habe ich Folgendes verwendet:

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

xls_file ist die Datei, die aus dem POST-Formular stammt. Ich hoffe, es hilft.

14
Moise

Verbinden Sie Ihre beiden Methoden, dies schlägt in Python 3.5.2 und Django 1.9 niemals fehl

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