Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import subprocess
- from reportlab.lib import colors, fonts
- from reportlab.lib.pagesizes import A4,landscape, portrait
- from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Frame, PageBreak
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
- from reportlab.lib.units import mm
- from reportlab.pdfbase.ttfonts import TTFont
- from reportlab.pdfbase import pdfmetrics
- from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
- from cells_table import Cells
- from json_file_io import JsonFileIo
- from Opiti.from_excel.classes.global_inc import registry
- class DaPdfReport:
- def __init__(self):
- self.setup_fonts()
- self.normal_para_style = ParagraphStyle('blabla', fontName="Times", fontSize=7, leading=7, alignment=TA_CENTER)
- self.da_print = self.get_da_print()
- print("self.da_print А", self.da_print)
- self.cell_formats = self.getCellFormats()
- self.s = {
- "pagesize": landscape(A4),
- "margin-left": 15 * mm,
- "margin-right": 15 * mm,
- "margin-top": 18 * mm,
- "margin-bottom": 15 * mm,
- }
- self.s["width"] = self.s["pagesize"][0]
- self.s["height"] = self.s["pagesize"][1]
- self.doc = SimpleDocTemplate("landscape.pdf", pagesize=self.s["pagesize"],
- rightMargin=self.s["margin-right"], leftMargin=self.s["margin-left"],
- topMargin=self.s["margin-top"], bottomMargin=self.s["margin-bottom"])
- self.elements = []
- self.elements.append(self.getRekwizitsTable())
- self.elements.extend(self.getTitles())
- tables = self.getTables()
- if (len(tables) < 2):
- print("Достатъчна е 1 страница, вмъкваме 1 таблица")
- self.elements.append(tables[0])
- print("Вмъкваме подписите")
- self.elements.append(self.getPodpisiTable())
- else:
- print("Нужни са 2 страници")
- print("Вмъкваме таблица 1")
- self.elements.append(tables[0])
- self.elements.extend(self.getPageOfN(1))
- print("Минаваме на нова страница")
- self.elements.append(PageBreak())
- print("Вмъкваме таблица 2")
- self.elements.append(tables[1])
- print("Вмъкваме подписите")
- self.elements.append(self.getPodpisiTable())
- self.elements.extend(self.getPageOfN(2))
- self.doc.build(self.elements)
- subprocess.Popen(['landscape.pdf'], shell=True)
- def getPodpisiTableHeight(self):
- return sum(self.getPodpisiRightRowHeights())
- def getPodpisiTable(self):
- print("Подписи")
- left = self.getPodpisiLeftTable()
- right = self.getPodpisiRightTable()
- table_data = [[left, "", right]]
- free_width = self.getFreeWidth()
- left_width = 50 * mm
- right_width = 100 * mm
- middle_width = A4[1] - self.s["margin-left"] - self.s["margin-right"] - left_width - right_width - free_width
- col_widths = [left_width, middle_width, right_width]
- table_height = self.getPodpisiTableHeight()
- row_heights = [table_height]
- table = Table(table_data, colWidths=col_widths, rowHeights=row_heights)
- table.setStyle(TableStyle([
- ('TEXTFONT', (0, 0), (-1, -1), 'Times'),
- ('FONTNAME', (0, 0), (-1, -1), 'Times'),
- ('FONT', (0, 0), (-1, -1), 'Times', 6, 7),
- ('LEFTPADDING', (0, 0), (-1, -1), 0),
- ('RIGHTPADDING', (0, 0), (-1, -1), 0),
- ('TOPPADDING', (0, 0), (-1, -1), 0),
- ('BOTTOMPADDING', (0, 0), (-1, -1), 0),
- ('VALIGN', (0, 0), (-1, -1), "TOP"),
- ('ALIGN', (0, 0), (-1, -1), "RIGHT"),
- ]))
- return table
- def getPodpisiLeftTable(self):
- print("Подписи ляво")
- table_data = [
- ['Ръководител: .............'],
- ['ДИМИТЪР ГЕОРГИЕВ ДЖАМБАЗОВ'],
- ]
- col_widths = [50 * mm]
- row_heights = [5 * mm, 5 * mm] # = 18
- table = Table(table_data, colWidths=col_widths, rowHeights=row_heights)
- table.setStyle(TableStyle([
- ('FONT', (0, 0), (0, 0), 'Timesbd', 8, 7), # Ръководител
- ('FONT', (0, 1), (0, 1), 'Times', 6, 7), # (Името)
- ]))
- return table
- def getPodpisiRightRowHeights(self):
- return [5 * mm, 5 * mm, 3 * mm, 3 * mm, 5 * mm]
- def getPodpisiRightTable(self):
- print("Подписи дясно")
- table_data = [
- ['Съставител: .............'],
- ['СП ОФИС ГЕОРГИЕВ ЕООД, Асеновград'],
- ['ЕИК: 115 882 107'],
- ['Представляващ: .............'],
- ['АЛЕКСАНДЪР ГЕОРГИЕВ ГЕОРГИЕВ']
- ]
- col_widths = [50 * mm]
- row_heights = self.getPodpisiRightRowHeights() # = 18
- table = Table(table_data, colWidths=col_widths, rowHeights=row_heights)
- table.setStyle(TableStyle([
- ('FONT', (0, 0), (0, 0), 'Timesbd', 8, 7), # Ръководител
- ('FONT', (0, 1), (0, -1), 'Times', 6, 7), # (Името)
- ('FONT', (0, 3), (0, 3), 'Timesbd', 8, 7), # Ръководител
- ]))
- return table
- def getEffSpan(self, o_c_1, o_r_1, o_c_2, o_r_2, da_print): # ('SPAN', (15, 0), (15, 1)),
- r = 0
- e_c_1 = None; e_r_1 = None
- e_c_2 = None; e_r_2 = None
- for row_idx in da_print:
- o_r = int(row_idx.replace("row_", "")) # напр. 5
- c = 0
- for col_idx in da_print[row_idx]:
- o_c = int(col_idx.replace("col_", ""))
- if o_r_1 <= o_r <= o_r_2 and o_c_1 <= o_c <= o_c_2:
- if e_c_1 is None:
- e_c_1 = c; e_c_2 = c
- e_r_1 = r; e_r_2 = r
- else:
- e_c_2 = c
- e_r_2 = r
- c += 1
- r += 1
- if e_c_1 is None:
- return None
- return [e_c_1, e_r_1, e_c_2, e_r_2]
- def getTables(self):
- podpisiTableHeight = self.getPodpisiTableHeight()
- row_heights = self.getRowHeights()
- # Под реквизиттите се побират точно пърите 27 реда [0..26] на пълната таблица
- height_limit = sum(row_heights[0:26:])
- page_x_of_y = 10 * mm
- # Очаквана височината на таблицата
- total_height = 0
- for row_idx in self.da_print:
- r = int(row_idx.replace("row_", "")) # напр. 5
- height = row_heights[r]
- total_height += height
- one_page_only = False
- if podpisiTableHeight + total_height <= height_limit:
- one_page_only = True
- max_table_height_page_1 = height_limit - page_x_of_y
- if one_page_only:
- print("Достатъчна е 1 страница")
- else:
- print("Нужни са 2 страници")
- groups = {}
- groups["dna"] = {
- "start_row_idx": "row_4",
- "end_row_idx": "row_9",
- "fits_on_page_1": None
- }
- groups["dma"] = {
- #"start_row": 10,
- #"end_row": 20,
- "start_row_idx": "row_10",
- "end_row_idx": "row_20",
- "fits_on_page_1": None
- }
- groups["dfa"] = {
- "start_row_idx": "row_21",
- "end_row_idx": "row_29",
- "fits_on_page_1": None
- }
- groups["tax"] = {
- "start_row_idx": "row_30",
- "end_row_idx": "row_30",
- "fits_on_page_1": None
- }
- groups["total"] = {
- "start_row_idx": "row_31",
- "end_row_idx": "row_31",
- "fits_on_page_1": None
- }
- # Фитлтрира групита така че да останат само тези, които реално присъстват
- groups = {g_idx: g for g_idx, g in groups.items() if g["start_row_idx"] in self.da_print}
- if one_page_only:
- for g_idx in groups:
- groups[g_idx]["fits_on_page_1"] = True
- else:
- for g_idx in groups:
- #start_row_idx = groups[g_idx]["start_row_idx"] # Изглежда ненужен
- end_row_idx = groups[g_idx]["end_row_idx"]
- groups[g_idx]["fits_on_page_1"] = False
- height = 0
- for row_idx in self.da_print:
- r = int(row_idx.replace("row_", "")) # напр. 5
- height += row_heights[r]
- if height > max_table_height_page_1:
- break
- if row_idx == end_row_idx:
- groups[g_idx]["fits_on_page_1"] = True
- groups_on_page1 = {g_idx: g for g_idx, g in groups.items() if g["fits_on_page_1"]}
- groups_count_total = len(groups)
- groups_count_page1 = len(groups_on_page1)
- if groups_count_total == groups_count_page1:
- last_group_idx = list(groups.keys())[-1] #напр "tax" или "dfa"
- groups[last_group_idx]["fits_on_page_1"] = False
- max_row_idx_page1 = 0
- for g_idx in groups:
- if groups[g_idx]["fits_on_page_1"]:
- max_row_idx_page1 = groups[g_idx]["end_row_idx"]
- max_row_page1 = int(max_row_idx_page1.replace("row_", ""))
- antetka_row_keys = ["row_0", "row_1", "row_2"]
- da_print_page1 = {}
- da_page1 = []
- row_heights_page1 = []
- da_print_page2 = {}
- da_page2 = []
- row_heights_page2 = []
- for r_idx in self.da_print:
- r = int(r_idx.replace("row_", ""))
- da_row = []
- for c_idx in self.da_print[r_idx]:
- c = int(c_idx.replace("col_", ""))
- v_idx = c_idx.replace("col_", "val_")
- val = self.da_print[r_idx][c_idx][v_idx]
- try:
- val = int(val)
- val = f"{val:,d}".replace(',', ' ')
- except:
- pass
- da_row.append(self.getCellParagraph(r, c, val))
- if r_idx in antetka_row_keys:
- da_print_page1[r_idx] = self.da_print[r_idx].copy()
- da_page1.append(da_row)
- row_heights_page1.append(row_heights[r])
- if one_page_only:
- pass # Няма нужда от стр 2
- else:
- da_print_page2[r_idx] = self.da_print[r_idx].copy()
- da_page2.append(da_row)
- row_heights_page2.append(row_heights[r])
- else:
- if r <= max_row_page1:
- da_print_page1[r_idx] = self.da_print[r_idx].copy()
- da_page1.append(da_row)
- row_heights_page1.append(row_heights[r])
- else:
- if one_page_only:
- pass # Няма нужда от стр 2
- else:
- da_print_page2[r_idx] = self.da_print[r_idx].copy()
- da_page2.append(da_row)
- row_heights_page2.append(row_heights[r])
- col_widths = [42 * mm]
- col_widths.extend([15 * mm] * 15)
- orig_spans = [
- ('SPAN', (0, 0), (0, 1)),
- ('SPAN', (1, 0), (7, 0)),
- ('SPAN', (8, 0), (14, 0)),
- ('SPAN', (15, 0), (15, 1)),
- ('SPAN', (0, 3), (15, 3)),
- ('SPAN', (5, 7), (6, 7)),
- ('SPAN', (5, 8), (6, 8)),
- ('SPAN', (8, 7), (14, 7)),
- ('SPAN', (8, 8), (14, 8)),
- ('SPAN', (0, 10), (15, 10)),
- ('SPAN', (8, 12), (14, 12)),
- ('SPAN', (5, 18), (6, 18)),
- ('SPAN', (5, 19), (6, 19)),
- ('SPAN', (8, 18), (14, 18)),
- ('SPAN', (8, 19), (14, 19)),
- ('SPAN', (0, 21), (15, 21)),
- ('SPAN', (8, 22), (14, 22)),
- ('SPAN', (8, 23), (14, 23)),
- ('SPAN', (8, 24), (14, 24)),
- ('SPAN', (8, 25), (14, 25)),
- ('SPAN', (8, 27), (14, 27)),
- ('SPAN', (8, 28), (14, 28)),
- ('SPAN', (8, 30), (14, 30)),
- ]
- table_page1 = Table(da_page1, colWidths=col_widths, rowHeights=row_heights_page1)
- table_styles_page1 = [
- ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
- ('BACKGROUND', (0, 0), (-1, 1), colors.lightgreen),
- ('ALIGN', (1, 4), (-1, -1), "RIGHT"),
- ('LEFTPADDING', (1, 1), (-1, 1), 2),
- ('RIGHTPADDING', (0, 0), (-1, -1), 2),
- ('TOPPADDING', (0, 0), (-1, -1), 2),
- ('BOTTOMPADDING', (0, 0), (-1, -1), 2),
- ('FONT', (0, 0), (-1, -1), 'Times', 7, 7),
- ('VALIGN', (0, 0), (-1, -1), "MIDDLE"),
- ('RIGHTPADDING', (1, 4), (-1, -1), 5),
- ]
- for os in orig_spans:
- es_raw = self.getEffSpan(os[1][0], os[1][1], os[2][0], os[2][1], da_print_page1)
- if es_raw:
- es = ("SPAN", (es_raw[0], es_raw[1]), (es_raw[2], es_raw[3]))
- table_styles_page1.append(es)
- table_page1.setStyle(TableStyle(table_styles_page1))
- if one_page_only:
- return [table_page1]
- table_page2 = Table(da_page2, colWidths=col_widths, rowHeights=row_heights_page2)
- table_styles_page2 = [
- ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
- ('BACKGROUND', (0, 0), (-1, 1), colors.lightgreen),
- ('ALIGN', (1, 4), (-1, -1), "RIGHT"),
- ('LEFTPADDING', (1, 1), (-1, 1), 2),
- ('RIGHTPADDING', (0, 0), (-1, -1), 2),
- ('TOPPADDING', (0, 0), (-1, -1), 2),
- ('BOTTOMPADDING', (0, 0), (-1, -1), 2),
- ('FONT', (0, 0), (-1, -1), 'Times', 6, 7),
- ('VALIGN', (0, 0), (-1, -1), "MIDDLE"),
- ('RIGHTPADDING', (1, 4), (-1, -1), 5),
- ]
- for os in orig_spans:
- es_raw = self.getEffSpan(os[1][0], os[1][1], os[2][0], os[2][1], da_print_page2)
- if es_raw:
- es = ("SPAN", (es_raw[0], es_raw[1]), (es_raw[2], es_raw[3]))
- print(f"os {os} =?= es {es}")
- table_styles_page2.append(es)
- table_page2.setStyle(TableStyle(table_styles_page2))
- return [table_page1, table_page2]
- def getTitles(self):
- titles = []
- ps = ParagraphStyle('blabla', fontName="Timesbd", fontSize=8, leading=12, alignment=TA_CENTER)
- titles.append(Paragraph("СПРАВКА ЗА НЕТЕКУЩИТЕ (ДЪЛГОТРАЙНИ) АКТИВИ", ps))
- ps = ParagraphStyle('blabla', fontName="Timesbd", fontSize=8, alignment=TA_CENTER)
- p = Paragraph("към 31.12.2022 година", ps)
- ps = ParagraphStyle('blabla', fontName="Timesi", fontSize=8, alignment=TA_RIGHT)
- right_p = Paragraph("(хил. лева)", ps)
- table_data = [["", p, right_p]]
- left_width = 50 * mm
- right_width = 50 * mm
- free_width = self.getFreeWidth()
- middle_width = A4[1] - self.s["margin-left"] - self.s["margin-right"] - left_width - right_width - free_width
- col_widths = [left_width, middle_width, right_width]
- table_height = 3 * mm
- row_heights = [table_height]
- table = Table(table_data, colWidths=col_widths, rowHeights=row_heights)
- table.setStyle(TableStyle([
- ('RIGHTPADDING', (0, 0), (-1, -1), 0),
- ('BOTTOMPADDING', (0, 0), (-1, -1), -1),
- ]))
- titles.append(table)
- return titles
- def getPageOfN(self, page_number):
- items = []
- ps = ParagraphStyle('blabla', fontName="Timesbd", fontSize=5, leading=5, alignment=TA_CENTER)
- items.append(Paragraph(" ", ps)) # е HTML код за "непренебрегваем" интервал
- ps = ParagraphStyle('blabla', fontName="Timesbd", fontSize=8, alignment=TA_CENTER, textColor='green')
- items.append(Paragraph(f"Страница {page_number} от 2", ps))
- return items
- def getRekwizitsTable(self):
- print("Реквизити")
- leftRekwizit = self.getDanniFirmaLeftTable()
- rightRekwizit = self.getDanniFirmaRightTable()
- table_data = [[leftRekwizit, "", rightRekwizit]]
- free_width = self.getFreeWidth()
- left_width = 50 * mm
- right_width = 100 * mm
- middle_width = A4[1] - self.s["margin-left"] - self.s["margin-right"] - left_width - right_width - free_width
- col_widths = [left_width, middle_width, right_width]
- row_heights = [22 * mm]
- table = Table(table_data, colWidths=col_widths, rowHeights=row_heights)
- table.setStyle(TableStyle([
- ('TEXTFONT', (0, 0), (-1, -1), 'Times'),
- ('FONTNAME', (0, 0), (-1, -1), 'Times'),
- ('FONT', (0, 0), (-1, -1), 'Times', 6, 7),
- ('LEFTPADDING', (0, 0), (-1, -1), 0),
- ('RIGHTPADDING', (0, 0), (-1, -1), 0),
- ('TOPPADDING', (0, 0), (-1, -1), 0),
- ('BOTTOMPADDING', (0, 0), (-1, -1), 0),
- ('VALIGN', (0, 0), (-1, -1), "TOP"),
- ('ALIGN', (0, 0), (-1, -1), "RIGHT"),
- ]))
- return table
- def getFreeWidth(self):
- single_data_cell_width = self.getSingleDataCellWidth()
- da_print_data_cols_cnt = len(self.da_print['row_3'])
- free_width = (16 - da_print_data_cols_cnt) * single_data_cell_width
- return free_width
- def getDanniFirmaLeftTable(self):
- print("Реквизити ляво")
- table_data = [
- ["ОФИС ГЕОРГИЕВ ЕООД"],
- ["(предприятие)"],
- ["4230 Асеновград, \nул. Цар Иван Асен II 47"],
- ["(седалище и адрес на управление)"],
- ]
- col_widths = [50 * mm]
- row_heights = [5 * mm, 3 * mm, 7 * mm, 3 * mm] # = 18
- table = Table(table_data, colWidths=col_widths, rowHeights=row_heights)
- table.setStyle(TableStyle([
- ('BOX', (0, 0), (0, 3), 0.25, colors.blueviolet),
- # ('BACKGROUND', (0, 0), (0, 3), colors.powderblue),
- ('FONT', (0, 0), (0, 0), 'Timesbd', 8, 7), # ОФИС ГЕОРГИЕВ ЕООД
- ('FONT', (0, 1), (0, 1), 'Timesi', 6, 7), # (предприятие)
- ('VALIGN', (0, 1), (0, 1), "TOP"), # (предприятие)
- ('TOPPADDING', (0, 1), (0, 1), 0), # (предприятие)
- ('FONT', (0, 2), (0, 2), 'Timesbd', 8, 9), # Ул Цар Иван Асен II 47
- ('FONT', (0, 3), (0, 3), 'Timesi', 6, 7), # седалище и адрес на управление)
- ('VALIGN', (0, 3), (0, 3), "TOP"), # (седалище и адрес на управление)
- ('TOPPADDING', (0, 3), (0, 3), 0), # (седалище и адрес на управление)
- ('FONT', (0, 4), (0, 4), 'Timesbd', 8, 7), # data)
- ]))
- return table
- def getDanniFirmaRightTable(self):
- print("Реквизити дясно")
- table_data = [
- [ "ПРИЛОЖЕНИЕ:", "№ 5 към НСС 1"],
- [ "ЕИК:", "115 882 107" ],
- ["Код на дейността:", "2512" ],
- ['Дата на съставяне:', '06.06.2022г.'],
- ]
- col_widths = [28 * mm, 22 * mm]
- row_heights = [5 * mm] * 4
- table = Table(table_data, colWidths=col_widths, rowHeights=row_heights)
- table.setStyle(TableStyle([
- ('FONT', (0, 0), (0, -1), 'Timesbd', 8, 7),
- ('ALIGN', (0, 0), (0, -1), 'RIGHT'),
- ('FONT', (1, 0), (1, -1), 'Times', 8, 7),
- ('BOX', (0, 0), (-1, 2), 0.25, colors.blueviolet),
- ]))
- return table
- def setup_fonts(self):
- print("Настройване шрифтове")
- pdfmetrics.registerFont(TTFont('Times', 'Times.ttf', 'UTF-8'))
- pdfmetrics.registerFont(TTFont('Timesbd', 'Timesbd.ttf', 'UTF-8'))
- pdfmetrics.registerFont(TTFont('Timesi', 'Timesi.ttf', 'UTF-8'))
- pdfmetrics.registerFont(TTFont('Timesbi', 'Timesbi.ttf', 'UTF-8'))
- def getCellFormats(self):
- f = Cells()
- for r in [0, 2]:
- for c in range(0, 15+1):
- f.setItem(r, c, {
- "fontName": "Timesbd",
- "alignment": TA_CENTER,
- })
- for r in [1]:
- for c in range(0, 15+1):
- f.setItem(r, c, {
- "fontName": "Times",
- "alignment": TA_CENTER,
- })
- for r in [1]:
- for c in [1, 4, 7, 8, 11, 14, 15]:
- f.setItem(r, c, {
- "fontName": "Timesbd",
- "alignment": TA_CENTER,
- })
- for r in range(4, 31+1):
- for c in range(1, 15 + 1):
- f.setItem(r, c, {
- "fontName": "Times",
- "alignment": TA_RIGHT,
- })
- for r in range(4, 31+1):
- for c in [1, 4, 7, 8, 11, 14, 15]:
- f.setItem(r, c, {
- "fontName": "Timesbd",
- "alignment": TA_RIGHT,
- })
- for r in [3, 9, 10, 20, 21, 29, 31]:
- for c in range(0, 15 + 1):
- cell_format = f.getItem(r, c)
- if cell_format:
- # Вероятно винаги влизаме тук
- cell_format["fontName"] = "Timesbd"
- f.setItem(r, c, cell_format)
- else:
- # Не очакваме да влезе тука
- f.setItem(r, c, {
- "fontName": "Timesbd",
- })
- for r in [9, 20, 29]:
- for c in [0]:
- cell_format = f.getItem(r, c)
- if cell_format:
- # Вероятно винаги влизаме тук
- cell_format["alignment"] = TA_RIGHT
- f.setItem(r, c, cell_format)
- else:
- # Не очакваме да влезе тука
- f.setItem(r, c, {
- "alignment": TA_RIGHT,
- })
- return f
- def getCellParagraph(self, r, c, text):
- cell_format = self.cell_formats.getItem(r, c)
- fontName = "Times"
- alignment = TA_LEFT
- if cell_format:
- if "fontName" in cell_format: fontName = cell_format["fontName"]
- if "alignment" in cell_format: alignment = cell_format["alignment"]
- ps = ParagraphStyle('blabla', fontName=fontName, fontSize=7, leading=7, alignment=alignment)
- return Paragraph(text, ps)
- def get_da_print(self):
- da_print = {}
- try:
- json_file_io = JsonFileIo(file_name=registry["da_print_combobox_test_file_name"])
- tmp_da_print = json_file_io.read_content()
- da_print = tmp_da_print
- except:
- print("File not found, giving up")
- quit()
- return da_print
- def getRowHeights(self):
- row_heights = [5 * mm, 8 * mm]
- h = 3.8
- row_heights.extend([h * mm, h * mm, h * mm, 9 * mm, h * mm, 9 * mm])
- row_heights.extend([h * mm, h * mm, h * mm, 6 * mm, h * mm, h * mm])
- row_heights.extend([h * mm, 6 * mm, h * mm, h * mm, 9 * mm, h * mm])
- row_heights.extend([h * mm, h * mm, 6 * mm, 6 * mm, 6 * mm, 6 * mm])
- row_heights.extend([6 * mm, h * mm, h * mm, h * mm, h * mm, 6 * mm])
- return row_heights
- def getSingleDataCellWidth(self):
- return 15 * mm
- def getLeadingCellWidth(self):
- return 42 * mm
- if __name__ == "__main__":
- # Създаваме обект, при което автоматично се изпълнява __init__(), която прави всичко
- DaPdfReport()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement