Download filelds as csv

Have done this before, but can not find it (and cant get it to work):

Where can I find code to download a CSV file?
I have been trying variations of this:

class SomeItemsCSV(BrowserView):
	"""Returns somethingin CSV format."""


	def download_csv_for_mytype(self):
		# Find all items of content type 'mytype'
		items = api.content.find(portal_type='my_type')
	
		
		data = BytesIO()
		fieldnames =  ['title']
		writer = csv.DictWriter()
		writer = csv.DictWriter(data, fieldnames=fieldnames)
		for item in items:
			title = item.Title
			writer.writerow({'title': title})

		# Return CSV data
		csv_content = data.getvalue()
		return csv_content


	def __call__(self):
		csv_string = self.download_csv_for_mytype()
	
		#csv_content = bytes(csv_string, 'utf-8')

		name = "my_items.csv"
		self.request.response.setHeader("Content-Type", "text/csv")
		self.request.response.setHeader(
			"Content-Disposition", f'attachment; filename="name.csv"'
		)
		self.request.response.setHeader("Content-Length", len(csv_string))
		self.request.response.write(csv_string)

What exactly is not working :thinking:

This last (posted as I left work) gives error:

 TypeError: __init__() missing 2 required positional arguments: 'f' and 'fieldnames'

But most of my tries (and all the suggestions from ChatGPT) error with TypeError: a bytes-like object is required, not 'str' (I think I tried with csv_content = bytes(csv_content, 'utf-8') .

Traceback (innermost last):
Module ZPublisher.WSGIPublisher, line 181, in transaction_pubevents
Module ZPublisher.WSGIPublisher, line 391, in publish_module
Module ZPublisher.WSGIPublisher, line 285, in publish
Module ZPublisher.mapply, line 98, in mapply
Module ZPublisher.WSGIPublisher, line 68, in call_object
Module DocentIMS.ActionItems.browser.exporter, line 103, in call
Module DocentIMS.ActionItems.browser.exporter, line 92, in download_csv_for_mytype
Module csv, line 143, in writeheader
Module csv, line 154, in writerow
TypeError: a bytes-like object is required, not 'str

This is a typical suggestion from ChatGPT

def download_csv_for_mytype(self):
	# Find all items of content type 'mytype'
	mytype_items = api.content.find(portal_type='my_type')
	
	
	if not mytype_items:
		return "No items of type 'mytype' found."

	# Prepare CSV data
	data = BytesIO()
	fieldnames = ['title', ]
	writer = csv.DictWriter(data, fieldnames=fieldnames)
	writer.writeheader()
	for item in mytype_items:
		title = item.Title()
		writer.writerow({'title': title,  })

	# Return CSV data
	csv_content = data.getvalue()
	return csv_content

# Example usage within Plone context
def __call__(self):
	csv_content = self.download_csv_for_mytype()

	self.request.response.setHeader("Content-Type", "text/csv")
	self.request.response.setHeader(
	  "Content-Disposition", 'attachment; filename="my_items.csv"'
	)
	self.request.response.setHeader("Content-Length", len(csv_content))
	self.request.response.write(csv_content)

	return self.request.response  # Ensure to return the response object

New day, will give it a new try :slight_smile:

Instead of using BS code generated by ChatGPT, just read the proper csv package documentation containing working examples and reasonable explanations. In this case: data = BytesIO() does not make sense. The docs contain a proper example.

2 Likes

Please don't use GPT AI (absent intelligence) for coding

1 Like

I got a bit frustrated trying to get it to work 'far too late at day', especially since I am almost certain I have done this once before, but cant find the code

What I do in my projects is to have an abstract CSVGenericView like so (because usually I need it more than once).

from plone import api
from Products.Five.browser import BrowserView

import csv
import io

class CSVGenericView(BrowserView):
    @property
    def records(self):
        return []

    @property
    def fileprefix(self):
        return "PREFIX"

    @property
    def filename(self):
        return f"{self.fileprefix}-{self.now():%Y-%m-%d %H:%M}.csv"

    def __call__(self):
        records = self.records
        if not records:
            api.portal.show_message(
                message="No data found for CSV.",
                request=self.request,
                type="error",
            )
            self.request.response.redirect(self.context.absolute_url())
            return "No data."
        self.request.response.setHeader("Content-Type", "text/csv")
        self.request.response.setHeader(
            "Content-Disposition", f"attachment;filename={self.filename}"
        )
        # UPDATE/EDITED
        # sio = io.BytesIO() 
        sio = io.StringIO()
        writer = csv.DictWriter(sio, records[0].keys())
        writer.writeheader()
        for record in records:
            writer.writerow(record)
        return sio.getvalue()

and then inherit from it i.e. like so (and register it in ZCML):


class CSVSomeDataView(CSVGenericView):

    @property
    def fileprefix(self):
        return "Some-Data"

    @property 
    def records(self):
        result = []
        brains = api.content.find(portal_type='my_type')
        for brain in brains:       
            obj = brain.getObject()
            # better use an OrderedDict below because in CSV order matters.
            result.append({"title": obj.Title(), "id": obj.getId()}) # and so on
       return result   

Actually I do not use CSV that much, as I learned customers prefer Excel exports. Ping me if you need an example.

"Therefore, in Python3, use io.StringIO, while in Python2 use io.BytesIO". If you're using Python 2.7, use BytesIO.

scratch-head curious why it works here....

I would not go that far, but AI must be used with care (for programming) :speak_no_evil:.

1 Like

Thanks a lot, unfortunately I get the same error for this:

Traceback (innermost last):
Module ZPublisher.WSGIPublisher, line 181, in transaction_pubevents
Module ZPublisher.WSGIPublisher, line 391, in publish_module
Module ZPublisher.WSGIPublisher, line 285, in publish
Module ZPublisher.mapply, line 98, in mapply
Module ZPublisher.WSGIPublisher, line 68, in call_object
Module XXX.YYY.browser.exporter, line 142, in call
Module csv, line 143, in writeheader
Module csv, line 154, in writerow
TypeError: a bytes-like object is required, not 'str'

PS: I think I got collective.excelexport working last year, but I would prefer CSV since they asked about that:

Comparing collective:master...espenmn:master · collective/collective.excelexport · GitHub

UPDATE: I pasted wrong error first time

UPDATE 2: It seems to work with

    sio = io.StringIO()
1 Like

Well, great, I copied the code from a project where I do not use it anymore, because all exports are excel meanwhile. In another project I have it running in Plone 6 with .... io.StringIO. Sorry for the noise. At least I can remove the code from the project now.

In doubt, write the data to a temporary file and return the file content from the view.

Not at all :slight_smile: Thanks for helping. I typically end up with 'such things' when I am supposed to go home (from work) and suddenly none of my code makes sense anymore.

1 Like