python - __iter__ - Cómo resolver "iterador debe devolver cadenas, no bytes"
iterator python 3 (3)
En Python 3, utilicé:
import csv
from io import StringIO
csvf = StringIO(xls_file.read().decode())
reader = csv.reader(csvf, delimiter='','')
xls_file siendo el archivo obtenido del formulario POST. Espero que ayude.
Estoy intentando importar un archivo CSV, usando un formulario para cargar el archivo desde el sistema cliente. Después de tener el archivo, tomaré partes de él y poblaré un modelo en mi aplicación. Sin embargo, aparece un error "el iterador debería devolver cadenas, no bytes" cuando voy a iterar sobre las líneas en el archivo cargado. Pasé horas probando cosas diferentes y leyendo todo lo que pude encontrar en esto, pero no puedo resolverlo (nota, soy relativamente nuevo en Django, que ejecuta 1.5 y python, que ejecuta 3.3). Desplegué las cosas para solucionar el error y lo ejecuté así para asegurarme de que todavía está allí. El error se muestra al ejecutar la línea "para clubes en club_list" en tools_clubs_import ():
El siguiente es el views.py corregido que funciona, según la respuesta marcada a continuación:
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'')
La siguiente es la versión original de lo que envié (el html que genera el formulario se incluye al final de esta lista de códigos)
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 %}
Valor de excepción:
El iterador debe devolver cadenas, no bytes (¿abrió el archivo en modo de texto?)
Ubicación de excepción: /usr/lib/python3.3/csv.py en nombres de campo, línea 96
Fusiona tus dos métodos, esto nunca falla en Python 3.5.2 y 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)
request.FILES
le proporciona archivos binarios , pero el módulo csv
quiere tener archivos en modo de texto.
io.TextIOWrapper()
envolver el archivo en una instancia de io.TextIOWrapper()
y debe averiguar la codificación:
from io import TextIOWrapper
f = TextIOWrapper(request.FILES[''filename''].file, encoding=request.encoding)
Probablemente sería mejor si tomara el parámetro charset
del encabezado Content-Type
si se proporciona; eso es lo que el cliente le dice que el conjunto de caracteres es.
No puede evitar la necesidad de conocer la codificación correcta para los datos del archivo; puede forzar la interpretación como ASCII, por ejemplo, al proporcionar también una palabra clave de errors
(configurándola como ''reemplazar'' o ''ignorar''), pero eso lleva a la pérdida de datos:
f = TextIOWrapper(request.FILES[''filename''].file, encoding=''ascii'', errors=''replace'')
El uso de TextIOWrapper solo funcionará cuando se use Django 1.11 y posterior (ya que este conjunto de cambios agregó el soporte requerido ). En versiones anteriores, puede aplicar un parche en el soporte después del hecho:
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