How do I create a CSV file from a model & then add it as an email attachment?
Question: I'm trying to generate a CSV from an object stored in my database and then add
the csv to an email as an attachment.

I'm using Django 1.7 & Python 2.7.

I'm getting this error with the following code: "AttributeError: '_csv.writer'
object has no attribute 'encode'"

Here's my code:

    
    
    def export_as_csv(report, fields, force_fields):
         print "export to csv started"
         """
        Generic csv export admin action.
        based on http://djangosnippets.org/snippets/2020/
        extended for being able to give list_display as fields and work with     admin-defined functions
        """
        opts = report
        if not force_fields:
            field_names = set([field.name for field in opts.fields])
            if fields:
            fieldset = set(fields)
                field_names = field_names & fieldset
        elif fields:
            field_names = set(fields)
        else:
            raise("option force_fields can only be used in parallel with option fields")
    
        writer = csv.writer(open("myfile.csv","w"), delimiter=',',quoting=csv.QUOTE_ALL)
    
        writer.writerow(list(field_names))
        row = []
        for field in field_names:
            try:
            row.append(unicode(getattr(report, field)).encode('utf-8'))
            except AttributeError:
                row.append(unicode((getattr(report, field)(report))).encode('utf-8'))
            except:
                raise
        writer.writerow(row)
    
        response = writer
        return response
    

The code below returns the above function's output as a parameter in
mail.attach(). The report parameter is my DB model/object that's created &
saved before any of this code is called.

    
    
    list_display = ('primary_id', 'created_date', 'report_size',)
    mail = EmailMessage('Email Subject', 'Email Body', None, ['example@example.com'])
    mail.attach("report.csv", export_as_csv(report, fields = list_display, force_fields=True) , "text/csv")
    mail.send()
    

Here's the model for a Report() object:

    
    
    class Report(models.Model):
        primary_id = models.IntegerField('Primary Key', primary_key = True, default=0) 
        created_date = models.DateTimeField('Created date', default=timezone.now)
        report_size = models.IntegerField('Report Size', default=0) 
    

Any help is greatly appreciated.

Here's how I ideally want this code to work: export_as_csv() returns a CSV
object that I don't need to save as a file or in my database - then the code
attaches that to the email directly from a CSV object in memory, not a saved
file. Not sure if this is possible, but that's ideal for my needs.


Answer: `csvwriter` can write to any object with a `write()` method, according to [the
docs](https://docs.python.org/2/library/csv.html#csv.writer). The usual Python
class to use when you want an in-memory filelike object is a
[`StringIO`](https://docs.python.org/2/library/stringio.html) instance, or
`cStringIO` if you're sure you won't be writing Unicode objects to it. So your
`export_to_csv` function should look something like:

    
    
    import cStringIO
    
    def export_as_csv(report, fields, force_fields):
        # define field_names as above, assuming your indentation is correct
        csv_buffer = cStringIO.StringIO()        
        writer = csv.writer(csv_buffer, delimiter=',', quoting=csv.QUOTE_ALL)
        field_names = list(field_names) # loses order, but at least it's consistent
        writer.writerow(field_names)
        row = []
        for field in field_names:
            row.append(getattr(report, field).encode('utf-8'))
        writer.writerow(row)
        return csv_buffer
    

Then something like:

    
    
    mail.attach("report.csv", export_as_csv(report, fields=list_display, force_fields=True).getvalue() , "text/csv")
    

Key differences are:

1) `csv_buffer` is an in-memory filelike object. Nothing is written to the
file system.

2) This handles field lookup for the simple model fields `'primary_id',
'created_date', 'report_size'` that you show in your example code. If you
actually need to handle the names of callables in your`field_names` sequence,
it gets harder.

3) This uses a single variable to hold the `field_names` after converting to a
list. It will probably work to use `list(field_names)` and `for field in
field_names` while `field_names` is a set, Python sets and dictionaries should
be order-stable as long as no modifications are made, but I find it clearer
and more reliable to be explicit about making sure the order syncs up.

4) This returns the `cStringIO.StringIO` object rather than the writer. The
writer's work is done once you've written everything you want to to it. The
`cStringIO` object, conversely, should return the buffered CSV contents when
you call `getvalue()` on it.

