django-commons / drf-excel

An XLSX spreadsheet renderer for Django REST Framework.
BSD 3-Clause "New" or "Revised" License
217 stars 40 forks source link

Fixed bugs when serialising nested serializers #28

Closed Shin-- closed 3 years ago

Shin-- commented 3 years ago

When serialising, we iterate through the headers we find in the first result:

elif isinstance(results, ReturnList) or type(results) is list:
  column_names_first_row = self._flatten(results[0])

Let's say the response looks like this:

{
  results: [
    {
      title: 'Example 1',
      some_relation: {
        id: 1,
        title: 'I am a relation'
      },
      some_value: 100
    },
    {
      title: 'Example 2',
      some_relation: null,
      some_value: 200
    }
  ]
}

This will lead to the headers title, some_relation.id, some_relation.title, some_value. While this works for the first result, the second row will have less values and therefore shift some_value to the left, ending up in the column of some_relation.title, with the one most right being empty.

A second problem would be when the second result in the example would actually be the first. In that case we would end up with incomplete headers.

I changed the flattening to use the serializer instead and iterate through all header keys for each result, to keep the correct order and not end up with shifted values.

I also added a property xlsx_use_labels that uses field labels instead of keys.

Update 25.03.2021:

Since this PR is still not merged or responded to, and I needed to add more functionality based on this commit, this is much more than a bugfix now:

# set xlsx_use_labels = True inside API view to enable labels
use_labels = getattr(drf_view, 'xlsx_use_labels', False)

# A list of header keys to ignore in our export
self.ignore_headers = getattr(drf_view, 'xlsx_ignore_headers', [])

# set dict named xlsx_use_labels inside API View. i.e. { True: 'Yes', False: 'No' }
self.boolean_display = getattr(drf_view, 'xlsx_boolean_labels', None)

# set dict named xlsx_date_format_mappings with headers as keys and formatting as value. i.e. { 'created_at': '%d.%m.%Y, %H:%M' }
self.date_format_mappings = getattr(drf_view, 'xlsx_date_format_mappings', None)

# Map a specific key to a column (i.e. if the field returns a json) or pass a function to format the value
# Example with key: { 'custom_choice': 'custom_choice.display' }, showing 'display' in the 'custom_choice' col
# Example with function { 'custom_choice': custom_func }, passing the value of 'custom_choice' to 'custom_func', allowing for formatting logic
self.custom_mappings = getattr(drf_view, 'xlsx_custom_mappings', None)
willtho89 commented 3 years ago

lgtm @FlipperPA do you have a timeframe for merging?

FlipperPA commented 3 years ago

@Shin-- @willtho89 I'm reviewing this today - work has been crazy and today is my first day off! The concept looks good, I just need to dive into the details.

FlipperPA commented 3 years ago

@Shin-- Thanks for the contribution! This has been released as version 0.4.0 on PyPI with a few fixes:

Thanks again - enjoy!