Question

Want to prompt browser to save csv

^^working off above question, file is exporting correctly but the data is not displaying correctly.

@view_config(route_name='csvfile', renderer='csv')
def csv(self):
name = DBSession.query(table).join(othertable).filter(othertable.id == 9701).all()
header = ['name']
rows = []
for item in name:
    rows = [item.id]
return {
    'header': header,
    'rows': rows
}

Getting _csv.Error Error: sequence expected but if I change in my renderer writer.writerows(value['rows']) to writer.writerow(value['rows']) the file will download via the browser just fine. Problem is, it's not displaying data in each row. The entire result/dataset is in one row, so each entry is in it's own column rather than it's own row.

Was it helpful?

Solution 2

Figured it out. kept getting Error: sequence expected so I was looking at the output. Decided to try putting the result inside another list.

@view_config(route_name='csv', renderer='csv')
def csv(self):
    d = datetime.now()
    query = DBSession.query(table, othertable).join(othertable).join(thirdtable).filter(
        thirdtable.sid == 9701)
    header = ['First Name', 'Last Name']
    rows = []
    filename = "csvreport" + d.strftime(" %m/%d").replace(' 0', '')
    for i in query:
        items = [i.table.first_name, i.table.last_name, i.othertable.login_time.strftime("%m/%d/%Y"),
    ]
    rows.append(items)
    return {
        'header': header,
        'rows': rows,
        'filename': filename
    }

This accomplishes 3 things. Fills out the header, fills the rows, and passes through a filename.

Renderer should look like this:

class CSVRenderer(object):
    def __init__(self, info):
        pass

    def __call__(self, value, system):
        fout = StringIO.StringIO()
        writer = csv.writer(fout, delimiter=',',quotechar =',',quoting=csv.QUOTE_MINIMAL)

        writer.writerow(value['header'])
        writer.writerows(value['rows'])

        resp = system['request'].response
        resp.content_type = 'text/csv'
        resp.content_disposition = 'attachment;filename='+value['filename']+'.csv'
        return fout.getvalue()

This way, you can use the same csv renderer anywhere else and be able to pass through your own filename. It's also the only way I could figure out how to get the data from one column in the database to iterate through one column in the renderer. It feels a bit hacky but it works and works well.

OTHER TIPS

First, I wonder if having a return statement inside your for loop isn't also causing problems; from the linked example it looks like their loop was in the prior statement.

I think what it looks like it's doing is it's building a collection of rows based on "table" having columns with the same name as the headers. What are the fields in your table table?

name = DBSession.query(table).join(othertable).filter(othertable.id == 9701).all()

This is going to give you back essentially a collection of rows from table, as if you did a SELECT query on it.

Something like

name = DBSession.query(table).join(othertable).filter(othertable.id == 9701).all()
header = ['name']
rows = []

for item in name:
  rows.append(item.name)

return {
        'header': header,
        'rows': r
        }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top