Search code examples
pythondjangocsvserializationtastypie

need a recursive CSV serializer for Django tastypie


I have created a simple GET only api for my Django application using tastypie. I need to deliver flat-tabular CSV data, but my database structure is normalized. Per the documentation I have implement a customized Serializer class with a to_csv() method as below.

def to_csv(self, data, options=None):
    options = options or {}
    data = self.to_simple(data, options)

    raw_data = StringIO.StringIO()
    writer = csv.writer(raw_data, quotechar="'", quoting=csv.QUOTE_NONNUMERIC)

    if "meta" in data.keys():#if multiple objects are returned
        objects = data.get("objects")
        writer.writerow(objects[0].keys())

        for object in objects:
            test = object.values()
            writer.writerow(test)
    else:
        writer.writerow(data.values())

    CSVContent=raw_data.getvalue()
    return CSVContent

This works great, except any resources get rendered by default as JSON (when I include full = True in the ModelResource ForeignKey specification), so I wind up with CSV data containing nested JSON data that looks like this.

foodID,foodName,related_details
1,"apricot","{'type':'fruit', 'cost':'medium'}"
2,"beef","{'type':'animal', 'cost':'high'}"
3,"celery","{'type':'vegetable', 'cost':'low'}"

My desired output is

foodID,foodName,type,cost
1,"apricot","fruit","medium"
2,"beef","animal","high"
3,"celery","vegetable","low"

I have an idea that I will need to apply my serializer recursively, and then to combine the results before writing to CSV, but have so far been unsuccessful.


Solution

  • def to_csv(self, data, options=None):
        options = options or {}
        data = self.to_simple(data, options)
    
        raw_data = StringIO.StringIO()
        first = True
    
        if "meta" in data.keys():#if multiple objects are returned
            objects = data.get("objects")
    
            for value in objects:
                test = {}
                self.flatten(value, test)
                if first:
                    writer = csv.DictWriter(raw_data, test.keys(), quotechar="'", quoting=csv.QUOTE_NONNUMERIC)
                    writer.writeheader()
                    writer.writerow(test)
                    first=False
                else:
                    writer.writerow(test)
        else:
            test = {}
            self.flatten(data, test)
            if first:
                writer = csv.DictWriter(raw_data, test.keys(), quotechar="'", quoting=csv.QUOTE_NONNUMERIC)
                writer.writeheader()
                writer.writerow(test)
                first=False
            else:
                writer.writerow(test)
        CSVContent=raw_data.getvalue()
        return CSVContent
    
    def flatten(self, data, odict = {}):
        if isinstance(data, list):
            for value in data:
                self.flatten(value, odict)
        elif isinstance(data, dict):
            for (key, value) in data.items():
                if not isinstance(value, (dict, list)):
                    odict[key] = value
                else:
                    self.flatten(value, odict)