Passed
Push — master ( a79136...7221f2 )
by Guangyu
02:17 queued 11s
created

excelexporters.spaceenergyitem   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 508
Duplicated Lines 28.94 %

Importance

Changes 0
Metric Value
eloc 376
dl 147
loc 508
rs 5.5199
c 0
b 0
f 0
wmc 56

3 Functions

Rating   Name   Duplication   Size   Complexity  
B export() 39 39 5
F generate_excel() 108 435 48
A group_by_category() 0 7 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like excelexporters.spaceenergyitem 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)] = '(' + category_dict_name + ') 分项消耗占比'
245
246
            current_row_number += 1
247
            table_start_row_number = current_row_number
248
249
            ws['B' + str(current_row_number)].fill = table_fill
250
251
            ws['C' + str(current_row_number)].font = name_small_font
252
            ws['C' + str(current_row_number)].fill = table_fill
253
            ws['C' + str(current_row_number)].alignment = c_c_alignment
254
            ws['C' + str(current_row_number)].border = f_border
255
            ws['C' + str(current_row_number)] = '消耗'
256
257
            current_row_number += 1
258
259
            for i in category_dict_values:
260
                ws['B' + str(current_row_number)].font = title_font
261
                ws['B' + str(current_row_number)].alignment = c_c_alignment
262
                ws['B' + str(current_row_number)].border = f_border
263
                ws['B' + str(current_row_number)] = \
264
                    reporting_period_data['names'][i] + " (" + reporting_period_data['units'][i] + ")"
265
                ws['C' + str(current_row_number)].font = name_font
266
                ws['C' + str(current_row_number)].alignment = c_c_alignment
267
                ws['C' + str(current_row_number)].border = f_border
268
                ws['C' + str(current_row_number)] = round(reporting_period_data['subtotals'][i], 2)
269
270
                current_row_number += 1
271
272
            table_end_row_number = current_row_number - 1
273
274
            pie = PieChart()
275
            pie.title = '(' + category_dict_name + ') 分项消耗占比'
276
            labels = Reference(ws, min_col=2, min_row=table_start_row_number + 1, max_row=table_end_row_number)
277
            pie_data = Reference(ws, min_col=3, min_row=table_start_row_number, max_row=table_end_row_number)
278
            pie.add_data(pie_data, titles_from_data=True)
279
            pie.set_categories(labels)
280
            pie.height = 5.25
281
            pie.width = 9
282
            s1 = pie.series[0]
283
            s1.dLbls = DataLabelList()
284
            s1.dLbls.showCatName = False
285
            s1.dLbls.showVal = True
286
            s1.dLbls.showPercent = True
287
            ws.add_chart(pie, 'D' + str(table_start_row_number))
288
289
            if len(category_dict_values) < 4:
290
                current_row_number = current_row_number - len(category_dict_values) + 4
291
292
            current_row_number += 1
293
294
        #####################################
295
296
        has_child_flag = True
297
        if "child_space" not in report.keys() or "energy_item_names" not in report['child_space'].keys() or \
298
                len(report['child_space']["energy_item_names"]) == 0:
299
            has_child_flag = False
300
301
        if has_child_flag:
302
            child = report['child_space']
303
304
            ws['B' + str(current_row_number)].font = title_font
305
            ws['B' + str(current_row_number)] = name + ' 子空间数据'
306
307
            current_row_number += 1
308
309
            ws['B' + str(current_row_number)].fill = table_fill
310
            ws['B' + str(current_row_number)].border = f_border
311
            ca_len = len(child['energy_item_names'])
312
313
            for i in range(0, ca_len):
314
                row = chr(ord('C') + i)
315
                ws[row + str(current_row_number)].fill = table_fill
316
                ws[row + str(current_row_number)].font = name_small_font
317
                ws[row + str(current_row_number)].alignment = c_c_alignment
318
                ws[row + str(current_row_number)].border = f_border
319
                ws[row + str(current_row_number)] = \
320
                    reporting_period_data['names'][i] + " (" + reporting_period_data['units'][i] + ")"
321
322
            space_len = len(child['child_space_names_array'][0])
323
324
            for i in range(0, space_len):
325
                current_row_number += 1
326
                row = str(current_row_number)
327
328
                ws['B' + row].font = name_font
329
                ws['B' + row].alignment = c_c_alignment
330
                ws['B' + row] = child['child_space_names_array'][0][i]
331
                ws['B' + row].border = f_border
332
333
                for j in range(0, ca_len):
334
                    col = chr(ord('C') + j)
335
                    ws[col + row].font = name_font
336
                    ws[col + row].alignment = c_c_alignment
337
                    ws[col + row] = child['subtotals_array'][j][i]
338
                    ws[col + row].border = f_border
339
340
            current_row_number += 1
341
342
            # Pie
343
            for i in range(0, ca_len):
344
                pie = PieChart()
345
                labels = Reference(ws, min_col=2, min_row=current_row_number - space_len,
346
                                   max_row=current_row_number - 1)
347
                pie_data = Reference(ws, min_col=3 + i, min_row=current_row_number - space_len - 1,
348
                                     max_row=current_row_number - 1)
349
                pie.add_data(pie_data, titles_from_data=True)
350
                pie.set_categories(labels)
351
                pie.height = 5.85
352
                pie.width = 8
353
                col = chr(ord('C') + i)
354
                pie.title = ws[col + str(current_row_number - space_len - 1)].value
355
                s1 = pie.series[0]
356
                s1.dLbls = DataLabelList()
357
                s1.dLbls.showCatName = False
358
                s1.dLbls.showVal = True
359
                s1.dLbls.showPercent = True
360
                chart_cell = ''
361
                if i % 2 == 0:
362
                    chart_cell = 'B' + str(current_row_number)
363
                else:
364
                    chart_cell = 'E' + str(current_row_number)
365
                    current_row_number += 6
366
                ws.add_chart(pie, chart_cell)
367
368
            current_row_number += 7
369
370
        #######################
371
372
        has_values_data = True
373
        has_timestamps_data = True
374
375
        if 'values' not in reporting_period_data.keys() or \
376
                reporting_period_data['values'] is None or \
377
                len(reporting_period_data['values']) == 0:
378
            has_values_data = False
379
380
        if 'timestamps' not in reporting_period_data.keys() or \
381
                reporting_period_data['timestamps'] is None or \
382
                len(reporting_period_data['timestamps']) == 0 or \
383
                len(reporting_period_data['timestamps'][0]) == 0:
384
            has_timestamps_data = False
385
386 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...
387
            ca_len = len(reporting_period_data['names'])
388
            time = reporting_period_data['timestamps'][0]
389
390
            ws['B' + str(current_row_number)].font = title_font
391
            ws['B' + str(current_row_number)] = name + ' 详细数据'
392
393
            current_row_number += 1
394
395
            chart_start_row_number = current_row_number
396
397
            current_row_number += ca_len * 6
398
            table_start_row_number = current_row_number
399
400
            ws['B' + str(current_row_number)].fill = table_fill
401
            ws['B' + str(current_row_number)].font = title_small_font
402
            ws['B' + str(current_row_number)].alignment = c_c_alignment
403
            ws['B' + str(current_row_number)].border = f_border
404
            ws['B' + str(current_row_number)] = '日期时间'
405
406
            col = 'C'
407
408
            for i in range(0, ca_len):
409
                ws[col + str(current_row_number)].fill = table_fill
410
                ws[col + str(current_row_number)].font = title_small_font
411
                ws[col + str(current_row_number)].alignment = c_c_alignment
412
                ws[col + str(current_row_number)].border = f_border
413
                ws[col + str(current_row_number)] = \
414
                    reporting_period_data['names'][i] + " (" + reporting_period_data['units'][i] + ")"
415
                col = chr(ord(col) + 1)
416
417
            current_row_number += 1
418
419
            for i in range(0, len(time)):
420
                ws['B' + str(current_row_number)].font = title_font
421
                ws['B' + str(current_row_number)].alignment = c_c_alignment
422
                ws['B' + str(current_row_number)].border = f_border
423
                ws['B' + str(current_row_number)] = time[i]
424
425
                col = 'C'
426
                for j in range(0, ca_len):
427
                    ws[col + str(current_row_number)].font = title_font
428
                    ws[col + str(current_row_number)].alignment = c_c_alignment
429
                    ws[col + str(current_row_number)].border = f_border
430
                    ws[col + str(current_row_number)] = round(reporting_period_data['values'][j][i], 2) \
431
                        if reporting_period_data['values'][j][i] is not None else 0.00
432
                    col = chr(ord(col) + 1)
433
434
                current_row_number += 1
435
436
            table_end_row_number = current_row_number - 1
437
438
            ws['B' + str(current_row_number)].font = title_font
439
            ws['B' + str(current_row_number)].alignment = c_c_alignment
440
            ws['B' + str(current_row_number)].border = f_border
441
            ws['B' + str(current_row_number)] = '小计'
442
443
            col = 'C'
444
445
            for i in range(0, ca_len):
446
                ws[col + str(current_row_number)].font = title_font
447
                ws[col + str(current_row_number)].alignment = c_c_alignment
448
                ws[col + str(current_row_number)].border = f_border
449
                ws[col + str(current_row_number)] = round(reporting_period_data['subtotals'][i], 2)
450
                col = chr(ord(col) + 1)
451
452
            current_row_number += 2
453
454
            format_time_width_number = 1.0
455
            min_len_number = 1.0
456
            min_width_number = 11.0  # format_time_width_number * min_len_number + 4 and min_width_number > 11.0
457
458
            if period_type == 'hourly':
459
                format_time_width_number = 4.0
460
                min_len_number = 2
461
                min_width_number = 12.0
462
            elif period_type == 'daily':
463
                format_time_width_number = 2.5
464
                min_len_number = 4
465
                min_width_number = 14.0
466
            elif period_type == 'monthly':
467
                format_time_width_number = 2.1
468
                min_len_number = 4
469
                min_width_number = 12.4
470
            elif period_type == 'yearly':
471
                format_time_width_number = 1.5
472
                min_len_number = 5
473
                min_width_number = 11.5
474
475
            for i in range(0, ca_len):
476
                bar = BarChart()
477
                bar.title = \
478
                    reporting_period_data['names'][i] + " (" + reporting_period_data['units'][i] + ")"
479
                labels = Reference(ws, min_col=2, min_row=table_start_row_number + 1, max_row=table_end_row_number)
480
                bar_data = Reference(ws, min_col=3 + i, min_row=table_start_row_number, max_row=table_end_row_number)
481
                bar.add_data(bar_data, titles_from_data=True)
482
                bar.set_categories(labels)
483
                bar.height = 5.85
484
                bar.width = format_time_width_number * len(time) if len(time) > min_len_number else min_width_number
485
                if bar.width > 24:
486
                    bar.width = 24
487
                bar.dLbls = DataLabelList()
488
                bar.dLbls.showVal = False
489
                bar.dLbls.showPercent = True
490
                chart_col = 'B'
491
                chart_cell = chart_col + str(chart_start_row_number)
492
                chart_start_row_number += 6
493
                ws.add_chart(bar, chart_cell)
494
495
    filename = str(uuid.uuid4()) + '.xlsx'
496
    wb.save(filename)
497
498
    return filename
499
500
501
def group_by_category(category_list):
502
    category_dict = dict()
503
    for i, value in enumerate(category_list):
504
        if value not in category_dict.keys():
505
            category_dict[value] = list()
506
        category_dict[value].append(i)
507
    return category_dict
508
509