Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python-docx how to merge row cells

I am creating a Word document from data using python-docx. I can create all the rows and cells with no problem but, in some cases, when the current record from the database has some content in field comment, I need to add a new line to display a long content.

I tried by appending a paragraph, but the result is that the comment is appended after the table, and I need it to be added bellow the current table row.

I think the solution is to append a table row with all cells merged, but I can't find documentation to do so.

This is the code where I generate the docx file:

class OperationDOCXView(viewsets.ViewSet):
    exclude_from_schema = True

    def list(self, request):
        from ReportsManagerApp.controllers import Operations2Controller

        self.profile_id = request.query_params['profile_id']
        self.operation_date = request.query_params['operation_date']
        self.operation_type = request.query_params['operation_type']
        self.format = request.query_params['doc_format']

        operation_report_controller = Operations2Controller(self.profile_id, self.operation_date, self.operation_type)
        context = operation_report_controller.get_context()

        if self.format == 'json':
            return Response(context)
        else:
            word_doc = self.get_operation_word_file(request, context)

        return Response("{}{}{}".format(request.get_host(), settings.MEDIA_URL, word_doc))

    def get_operation_word_file(self, request, context):
        import unicodedata
        from django.core.files import File
        from django.urls import reverse
        from docx import Document
        from docx.shared import Inches, Pt

        operation_type = {
            'arrival': 'Llegadas',
            'departure': 'Salidas',
            'hotel': 'Hotel-Hotel',
            'tour': 'Tours',
        }

        weekdays = {
            '0': 'LUNES',
            '1': 'MARTES',
            '2': 'MIÉRCOLES',
            '3': 'JUEVES',
            '4': 'VIERNES',
            '5': 'SÁBADO',
            '6': 'DOMINGO',
        }

        titles = ['Booking', 'Nombre', '#', 'Vuelo', 'Hr', 'P Up', 'Traslado', 'Circuito', 'Priv?', 'Agencia', '']
        widths = [Inches(1), Inches(2), Inches(0.5), Inches(1), Inches(1), Inches(1), Inches(2), Inches(3), Inches(0.5), Inches(3), Inches(0.5)]

        document = Document()

        section = document.sections[-1]
        section.top_margin = Inches(0.5)
        section.bottom_margin = Inches(0.5)
        section.left_margin = Inches(0.3)
        section.right_margin = Inches(0.2)

        style = document.styles['Normal']
        font = style.font
        font.name ='Arial'
        font.size = Pt(10)

        company_paragraph = document.add_heading("XXXX TTOO INC")
        company_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER

        description_paragraph = document.add_paragraph("Operación de {} del día {}".format(operation_type[self.operation_type], self.operation_date))
        description_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
        operation_date = self.get_operation_date().date()
        operation_week_day = operation_date.weekday()
        day_paragraph = document.add_paragraph(weekdays[str(operation_week_day)])
        day_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER

        for provider_unit, transfers in context.items():
            provider_unit_paragraph = document.add_paragraph(provider_unit)
            provider_unit_paragraph.style.font.size = Pt(10)
            provider_unit_paragraph.style.font.bold = False

            table = document.add_table(rows=1, cols=11)
            hdr_cells = table.rows[0].cells
            runs = []
            for i in range(len(hdr_cells)):
                runs.append(self.get_hdr_cells_run(hdr_cells[i], titles[i]))
            for row in table.rows:
                for idx, width in enumerate(widths):
                    row.cells[idx].width = width
            adults = 0
            minors = 0
            for transfer in transfers:
                # table = document.add_table(rows=1, cols=11)
                row_cells = table.add_row().cells
                row_cells[0].text = transfer['booking']
                row_cells[1].text = transfer['people']
                row_cells[2].text = transfer['pax']
                flight = transfer.get("flight","") if transfer.get("flight","") is not None else ""
                row_cells[3].text = flight
                flight_time = self.get_flight_time(flight) if flight != '' else ''
                row_cells[4].text = flight_time
                row_cells[5].text = transfer['pickup_time'].strftime('%H:%M') if transfer['pickup_time'] is not None else ''
                row_cells[6].text = transfer['place']
                row_cells[7].text = transfer['roundtrip']
                row_cells[8].text = transfer['is_private']
                row_cells[9].text = transfer['agency']
                people = transfer['pax'].split('.')
                adults = adults + int(people[0])
                minors = minors + int(people[1])

                if transfer['comment'] is not None:
                    document.add_paragraph("Comentarios: {}".format(transfer['comment']))

            for row in table.rows:
                for idx, width in enumerate(widths):
                    row.cells[idx].width = width
                for cell in row.cells:
                    paragraphs = cell.paragraphs
                    for paragraph in paragraphs:
                        for run in paragraph.runs:
                            font = run.font
                            font.size = Pt(8)

            row_cells = table.add_row().cells
            row_cells[10].text = "{}.{}".format(adults, minors)

        current_directory = settings.MEDIA_DIR
        file_name = "Operaciones {} {}.docx".format(self.operation_type, self.operation_date)
        document.save("{}{}".format(current_directory, file_name))

        return file_name

    def get_flight_time(self, flight):
        from OperationsManagerApp.models import Flight

        operation_types = {
            'arrival': 'ARRIVAL',
            'departure': 'DEPARTURE'
        }

        operation_date = datetime.strptime(self.operation_date, '%Y-%m-%d')

        try:
            flight = Flight.objects.get(flight_type=operation_types[self.operation_type], number=flight)
        except:
            return ''
        else:
            weekday_times = {
                '0': flight.time_monday,
                '1': flight.time_tuesday,
                '2': flight.time_wednesday,
                '3': flight.time_thursday,
                '4': flight.time_friday,
                '5': flight.time_saturday,
                '6': flight.time_sunday,
            }

        weekday_time = weekday_times[str(operation_date.weekday())]

        return weekday_time.strftime('%H:%M') if weekday_time is not None else ''

    def get_hdr_cells_run(self, hdr_cells, title):
        from docx.shared import Pt

        new_run = hdr_cells.paragraphs[0].add_run(title)
        new_run.bold = True
        new_run.font.size = Pt(8)

        return new_run

    def get_operation_date(self):
        date_array = self.operation_date.split('-')
        day = int(date_array[2])
        month = int(date_array[1])
        year = int(date_array[0])

        operation_date = datetime(year, month, day)

        return operation_date
like image 713
HuLu ViCa Avatar asked Mar 04 '23 03:03

HuLu ViCa


1 Answers

One approach is to add a paragraph to one of the cells:

cell.add_paragraph(transfer['comment'])

This will place it in the right position with respect to the row it belongs to, rather than after the table.

If that would take too much room for a single cell that already has another data item in it and you want to add a row, you'll need to account for that when you allocate the table. But assuming you get that worked out, merging the cells is easy:

row.cells[0].merge(row.cells[-1])
like image 125
scanny Avatar answered Mar 16 '23 06:03

scanny