Passed
Push — master ( 543cf0...8d30b3 )
by Guangyu
02:37 queued 13s
created

excelexporters.tenantenergyitem.generate_excel()   F

Complexity

Conditions 39

Size

Total Lines 363
Code Lines 276

Duplication

Lines 108
Ratio 29.75 %

Importance

Changes 0
Metric Value
cc 39
eloc 276
nop 5
dl 108
loc 363
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like excelexporters.tenantenergyitem.generate_excel() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import base64
2
import uuid
3
import os
4
from openpyxl.chart import (
5
    PieChart,
6
    BarChart,
7
    Reference,
8
)
9
from openpyxl.styles import PatternFill, Border, Side, Alignment, Font
10
from openpyxl.drawing.image import Image
11
from openpyxl import Workbook
12
from openpyxl.chart.label import DataLabelList
13
14
15
####################################################################################################################
16
# PROCEDURES
17
# Step 1: Validate the report data
18
# Step 2: Generate excel file
19
# Step 3: Encode the excel file bytes to Base64
20
####################################################################################################################
21
22
23 View Code Duplication
def export(report,
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
24
           name,
25
           reporting_start_datetime_local,
26
           reporting_end_datetime_local,
27
           period_type):
28
    ####################################################################################################################
29
    # Step 1: Validate the report data
30
    ####################################################################################################################
31
    if report is None:
32
        return None
33
    print(report)
34
35
    ####################################################################################################################
36
    # Step 2: Generate excel file from the report data
37
    ####################################################################################################################
38
    filename = generate_excel(report,
39
                              name,
40
                              reporting_start_datetime_local,
41
                              reporting_end_datetime_local,
42
                              period_type)
43
    ####################################################################################################################
44
    # Step 3: Encode the excel file to Base64
45
    ####################################################################################################################
46
    try:
47
        with open(filename, 'rb') as binary_file:
48
            binary_file_data = binary_file.read()
49
    except IOError as ex:
50
        pass
51
52
    # Base64 encode the bytes
53
    base64_encoded_data = base64.b64encode(binary_file_data)
0 ignored issues
show
introduced by
The variable binary_file_data does not seem to be defined for all execution paths.
Loading history...
54
    # get the Base64 encoded data using human-readable characters.
55
    base64_message = base64_encoded_data.decode('utf-8')
56
    # delete the file from server
57
    try:
58
        os.remove(filename)
59
    except NotImplementedError as ex:
60
        pass
61
    return base64_message
62
63
64
def generate_excel(report,
65
                   name,
66
                   reporting_start_datetime_local,
67
                   reporting_end_datetime_local,
68
                   period_type):
69
    wb = Workbook()
70
    ws = wb.active
71
72
    # Row height
73
    ws.row_dimensions[1].height = 102
74
    for i in range(2, 2000 + 1):
75
        ws.row_dimensions[i].height = 30
76
77
    # Col width
78
    ws.column_dimensions['A'].width = 1.5
79
80
    ws.column_dimensions['B'].width = 25.0
81
82
    for i in range(ord('C'), ord('L')):
83
        ws.column_dimensions[chr(i)].width = 15.0
84
85
    # Font
86
    name_font = Font(name='Constantia', size=15, bold=True)
87
    name_small_font = Font(name='Constantia', size=10, bold=True)
88
    title_font = Font(name='宋体', size=15, bold=True)
89
    title_small_font = Font(name='宋体', size=10, bold=True)
90
    data_font = Font(name='Franklin Gothic Book', size=11)
91
92
    table_fill = PatternFill(fill_type='solid', fgColor='1F497D')
93
    f_border = Border(left=Side(border_style='medium', color='00000000'),
94
                      right=Side(border_style='medium', color='00000000'),
95
                      bottom=Side(border_style='medium', color='00000000'),
96
                      top=Side(border_style='medium', color='00000000')
97
                      )
98
    b_border = Border(
99
        bottom=Side(border_style='medium', color='00000000'),
100
    )
101
102
    b_c_alignment = Alignment(vertical='bottom',
103
                              horizontal='center',
104
                              text_rotation=0,
105
                              wrap_text=False,
106
                              shrink_to_fit=False,
107
                              indent=0)
108
    c_c_alignment = Alignment(vertical='center',
109
                              horizontal='center',
110
                              text_rotation=0,
111
                              wrap_text=False,
112
                              shrink_to_fit=False,
113
                              indent=0)
114
    b_r_alignment = Alignment(vertical='bottom',
115
                              horizontal='right',
116
                              text_rotation=0,
117
                              wrap_text=False,
118
                              shrink_to_fit=False,
119
                              indent=0)
120
    c_r_alignment = Alignment(vertical='bottom',
121
                              horizontal='center',
122
                              text_rotation=0,
123
                              wrap_text=False,
124
                              shrink_to_fit=False,
125
                              indent=0)
126
    # Img
127
    img = Image("excelexporters/myems.png")
128
    img.width = img.width * 0.85
129
    img.height = img.height * 0.85
130
    # img = Image("myems.png")
131
    ws.add_image(img, 'B1')
132
133
    # Title
134
    ws.merge_cells('B3:I3')
135
    ws['B3'].font = name_font
136
    ws['B3'].alignment = b_c_alignment
137
    ws['B3'] = 'Name: ' + name + '     Period: ' + period_type + \
138
               '     Date: ' + reporting_start_datetime_local + "__" + reporting_end_datetime_local
139
140
    if "reporting_period" not in report.keys() or \
141
            "names" not in report['reporting_period'].keys() or len(report['reporting_period']['names']) == 0:
142
        filename = str(uuid.uuid4()) + '.xlsx'
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable str does not seem to be defined.
Loading history...
143
        wb.save(filename)
144
145
        return filename
146
147
    ##################################
148
149
    current_row_number = 6
150
151
    reporting_period_data = report['reporting_period']
152
153
    has_names_data_flag = True
154
155
    if "names" not in reporting_period_data.keys() or \
156
            reporting_period_data['names'] is None or \
157
            len(reporting_period_data['names']) == 0:
158
        has_names_data_flag = False
159
160
    if has_names_data_flag:
161
        ws['B' + str(current_row_number)].font = title_font
162
        ws['B' + str(current_row_number)] = name + ' 报告期消耗'
163
164
        current_row_number += 1
165
166
        category = reporting_period_data['names']
167
        ca_len = len(category)
168
169
        ws['B' + str(current_row_number)].fill = table_fill
170
171
        col = 'C'
172
173
        for i in range(0, ca_len):
174
            ws[col + str(current_row_number)].fill = table_fill
175
            ws[col + str(current_row_number)].font = name_small_font
176
            ws[col + str(current_row_number)].alignment = c_c_alignment
177
            ws[col + str(current_row_number)].border = f_border
178
            ws[col + str(current_row_number)] = \
179
                reporting_period_data['names'][i] + " " + reporting_period_data['energy_category_names'][i] + \
180
                " (" + reporting_period_data['units'][i] + ")"
181
182
            col = chr(ord(col) + 1)
183
184
        current_row_number += 1
185
186
        ws['B' + str(current_row_number)].font = title_font
187
        ws['B' + str(current_row_number)].alignment = c_c_alignment
188
        ws['B' + str(current_row_number)].border = f_border
189
        ws['B' + str(current_row_number)] = '消耗'
190
191
        col = 'C'
192
193
        for i in range(0, ca_len):
194
            ws[col + str(current_row_number)].font = name_font
195
            ws[col + str(current_row_number)].alignment = c_c_alignment
196
            ws[col + str(current_row_number)].border = f_border
197
            ws[col + str(current_row_number)] = round(reporting_period_data['subtotals'][i], 2)
198
199
            col = chr(ord(col) + 1)
200
201
        current_row_number += 1
202
203
        ws['B' + str(current_row_number)].font = title_font
204
        ws['B' + str(current_row_number)].alignment = c_c_alignment
205
        ws['B' + str(current_row_number)].border = f_border
206
        ws['B' + str(current_row_number)] = '单位面积值'
207
208
        col = 'C'
209
210
        for i in range(0, ca_len):
211
            ws[col + str(current_row_number)].font = name_font
212
            ws[col + str(current_row_number)].alignment = c_c_alignment
213
            ws[col + str(current_row_number)].border = f_border
214
            ws[col + str(current_row_number)] = round(reporting_period_data['subtotals_per_unit_area'][i], 2)
215
216
            col = chr(ord(col) + 1)
217
218
        current_row_number += 1
219
220
        ws['B' + str(current_row_number)].font = title_font
221
        ws['B' + str(current_row_number)].alignment = c_c_alignment
222
        ws['B' + str(current_row_number)].border = f_border
223
        ws['B' + str(current_row_number)] = '环比'
224
225
        col = 'C'
226
227
        for i in range(0, ca_len):
228
            ws[col + str(current_row_number)].font = name_font
229
            ws[col + str(current_row_number)].alignment = c_c_alignment
230
            ws[col + str(current_row_number)].border = f_border
231
            ws[col + str(current_row_number)] = str(
232
                round(reporting_period_data['increment_rates'][i] * 100, 2)) + '%' \
233
                if reporting_period_data['increment_rates'][i] is not None else '-'
234
235
            col = chr(ord(col) + 1)
236
237
        current_row_number += 2
238
239
        category_dict = group_by_category(reporting_period_data['energy_category_names'])
240
241
        for category_dict_name, category_dict_values in category_dict.items():
242
243
            ws['B' + str(current_row_number)].font = title_font
244
            ws['B' + str(current_row_number)] = \
245
                category_dict_name + ' (' + reporting_period_data['units'][category_dict_values[0]] + \
246
                                     ') 分项消耗占比'
247
248
            current_row_number += 1
249
            table_start_row_number = current_row_number
250
251
            ws['B' + str(current_row_number)].fill = table_fill
252
253
            ws['C' + str(current_row_number)].font = name_small_font
254
            ws['C' + str(current_row_number)].fill = table_fill
255
            ws['C' + str(current_row_number)].alignment = c_c_alignment
256
            ws['C' + str(current_row_number)].border = f_border
257
            ws['C' + str(current_row_number)] = '消耗'
258
259
            current_row_number += 1
260
261
            for i in category_dict_values:
262
                ws['B' + str(current_row_number)].font = title_font
263
                ws['B' + str(current_row_number)].alignment = c_c_alignment
264
                ws['B' + str(current_row_number)].border = f_border
265
                ws['B' + str(current_row_number)] = \
266
                    reporting_period_data['names'][i] + " (" + reporting_period_data['units'][i] + ")"
267
                ws['C' + str(current_row_number)].font = name_font
268
                ws['C' + str(current_row_number)].alignment = c_c_alignment
269
                ws['C' + str(current_row_number)].border = f_border
270
                ws['C' + str(current_row_number)] = round(reporting_period_data['subtotals'][i], 2)
271
272
                current_row_number += 1
273
274
            table_end_row_number = current_row_number - 1
275
276
            pie = PieChart()
277
            pie.title = \
278
                category_dict_name + ' (' + reporting_period_data['units'][category_dict_values[0]] + \
279
                                     ') 分项消耗占比'
280
            labels = Reference(ws, min_col=2, min_row=table_start_row_number + 1, max_row=table_end_row_number)
281
            pie_data = Reference(ws, min_col=3, min_row=table_start_row_number, max_row=table_end_row_number)
282
            pie.add_data(pie_data, titles_from_data=True)
283
            pie.set_categories(labels)
284
            pie.height = 5.25
285
            pie.width = 9
286
            s1 = pie.series[0]
287
            s1.dLbls = DataLabelList()
288
            s1.dLbls.showCatName = False
289
            s1.dLbls.showVal = True
290
            s1.dLbls.showPercent = True
291
            ws.add_chart(pie, 'D' + str(table_start_row_number))
292
293
            if len(category_dict_values) < 4:
294
                current_row_number = current_row_number - len(category_dict_values) + 4
295
296
            current_row_number += 1
297
298
        #######################
299
300
        has_values_data = True
301
        has_timestamps_data = True
302
303
        if 'values' not in reporting_period_data.keys() or \
304
                reporting_period_data['values'] is None or \
305
                len(reporting_period_data['values']) == 0:
306
            has_values_data = False
307
308
        if 'timestamps' not in reporting_period_data.keys() or \
309
                reporting_period_data['timestamps'] is None or \
310
                len(reporting_period_data['timestamps']) == 0 or \
311
                len(reporting_period_data['timestamps'][0]) == 0:
312
            has_timestamps_data = False
313
314 View Code Duplication
        if has_values_data and has_timestamps_data:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
315
            ca_len = len(reporting_period_data['names'])
316
            time = reporting_period_data['timestamps'][0]
317
318
            ws['B' + str(current_row_number)].font = title_font
319
            ws['B' + str(current_row_number)] = name + ' 详细数据'
320
321
            current_row_number += 1
322
323
            chart_start_row_number = current_row_number
324
325
            current_row_number += ca_len * 6
326
            table_start_row_number = current_row_number
327
328
            ws['B' + str(current_row_number)].fill = table_fill
329
            ws['B' + str(current_row_number)].font = title_small_font
330
            ws['B' + str(current_row_number)].alignment = c_c_alignment
331
            ws['B' + str(current_row_number)].border = f_border
332
            ws['B' + str(current_row_number)] = '日期时间'
333
334
            col = 'C'
335
336
            for i in range(0, ca_len):
337
                ws[col + str(current_row_number)].fill = table_fill
338
                ws[col + str(current_row_number)].font = title_small_font
339
                ws[col + str(current_row_number)].alignment = c_c_alignment
340
                ws[col + str(current_row_number)].border = f_border
341
                ws[col + str(current_row_number)] = \
342
                    reporting_period_data['names'][i] + " (" + reporting_period_data['units'][i] + ")"
343
                col = chr(ord(col) + 1)
344
345
            current_row_number += 1
346
347
            for i in range(0, len(time)):
348
                ws['B' + str(current_row_number)].font = title_font
349
                ws['B' + str(current_row_number)].alignment = c_c_alignment
350
                ws['B' + str(current_row_number)].border = f_border
351
                ws['B' + str(current_row_number)] = time[i]
352
353
                col = 'C'
354
                for j in range(0, ca_len):
355
                    ws[col + str(current_row_number)].font = title_font
356
                    ws[col + str(current_row_number)].alignment = c_c_alignment
357
                    ws[col + str(current_row_number)].border = f_border
358
                    ws[col + str(current_row_number)] = round(reporting_period_data['values'][j][i], 2) \
359
                        if reporting_period_data['values'][j][i] is not None else 0.00
360
                    col = chr(ord(col) + 1)
361
362
                current_row_number += 1
363
364
            table_end_row_number = current_row_number - 1
365
366
            ws['B' + str(current_row_number)].font = title_font
367
            ws['B' + str(current_row_number)].alignment = c_c_alignment
368
            ws['B' + str(current_row_number)].border = f_border
369
            ws['B' + str(current_row_number)] = '小计'
370
371
            col = 'C'
372
373
            for i in range(0, ca_len):
374
                ws[col + str(current_row_number)].font = title_font
375
                ws[col + str(current_row_number)].alignment = c_c_alignment
376
                ws[col + str(current_row_number)].border = f_border
377
                ws[col + str(current_row_number)] = round(reporting_period_data['subtotals'][i], 2)
378
                col = chr(ord(col) + 1)
379
380
            current_row_number += 2
381
382
            format_time_width_number = 1.0
383
            min_len_number = 1.0
384
            min_width_number = 11.0  # format_time_width_number * min_len_number + 4 and min_width_number > 11.0
385
386
            if period_type == 'hourly':
387
                format_time_width_number = 4.0
388
                min_len_number = 2
389
                min_width_number = 12.0
390
            elif period_type == 'daily':
391
                format_time_width_number = 2.5
392
                min_len_number = 4
393
                min_width_number = 14.0
394
            elif period_type == 'monthly':
395
                format_time_width_number = 2.1
396
                min_len_number = 4
397
                min_width_number = 12.4
398
            elif period_type == 'yearly':
399
                format_time_width_number = 1.5
400
                min_len_number = 5
401
                min_width_number = 11.5
402
403
            for i in range(0, ca_len):
404
                bar = BarChart()
405
                bar.title = \
406
                    reporting_period_data['names'][i] + " (" + reporting_period_data['units'][i] + ")"
407
                labels = Reference(ws, min_col=2, min_row=table_start_row_number + 1, max_row=table_end_row_number)
408
                bar_data = Reference(ws, min_col=3 + i, min_row=table_start_row_number, max_row=table_end_row_number)
409
                bar.add_data(bar_data, titles_from_data=True)
410
                bar.set_categories(labels)
411
                bar.height = 5.85
412
                bar.width = format_time_width_number * len(time) if len(time) > min_len_number else min_width_number
413
                if bar.width > 24:
414
                    bar.width = 24
415
                bar.dLbls = DataLabelList()
416
                bar.dLbls.showVal = False
417
                bar.dLbls.showPercent = True
418
                chart_col = 'B'
419
                chart_cell = chart_col + str(chart_start_row_number)
420
                chart_start_row_number += 6
421
                ws.add_chart(bar, chart_cell)
422
423
    filename = str(uuid.uuid4()) + '.xlsx'
424
    wb.save(filename)
425
426
    return filename
427
428
429
def group_by_category(category_list):
430
    category_dict = dict()
431
    for i, value in enumerate(category_list):
432
        if value not in category_dict.keys():
433
            category_dict[value] = list()
434
        category_dict[value].append(i)
435
    return category_dict
436