به نام اندیشه پاک
در این آموزش قصد داریم بهصورت تصویری و قدمبهقدم، نحوه تولید یک گزارش که شامل متنهای فارسی است را به کمک پایگاه داده MySQL و زبان برنامهنویسی پایتون۳.۵ نشان دهیم.
کلیه کدهای منبع مثال ارائهشده از آدرس زیر قابل دریافت و استفاده هستند.
https://gitlab.com/pbarjoueian/report_python35
در این آموزش، برای گزارشگیری از پکیج(کتابخانه) reportlab استفاده میکنیم که یکی از قدرتمندترین کتابخانهها در این زمینه است و شرکتهای بزرگی مانند HP نیز از آن استفاده میکنند.
مرحله ۱: در MySQL، توسط دستور زیر پایگاه دادهای به نام reportlab_db برای استفاده در برنامه ایجاد میکنیم. (آموزش نصب و اجرای قدمبهقدم ایجاد پایگاه داده، جدول و واردکردن دادههای موردنیاز در نرمافزار مدیریت پایگاه داده MySQL، در ضمیمه نشان دادهشده است.)
CREATE DATABASE reportlab_db;
مرحله ۲: توسط دستور زیر، جدولی به نام persons در آن ایجاد میکنیم که شامل ستونهای id، fname(نام)، lname(نام خانوادگی)، phone(شماره تلفن) و address(آدرس) مربوط به کارکنان است.
CREATE TABLE `persons` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `fname` varchar(45) NOT NULL, `lname` varchar(45) NOT NULL, `phone` varchar(45) NOT NULL, `address` varchar(45) NOT NULL, PRIMARY KEY (`id`) )
مرحله ۳: پسازآن به کمک دستور زیر چند ردیف یا سطر به این جدول اضافه میکنیم.
INSERT INTO `persons` VALUES (1,'پیمان','برجوییان','۰۹۱۳۱۱۱۱۱۱۱','اصفهان'), (2,'محمدحسین','خدادوستان','۰۹۱۳۲۲۲۲۲۲۲','اصفهان '), (3,'محمدصادق','راتق','۰۹۳۸۳۳۳۳۳۳۳','اصفهان'), (4,'علیرضا','جواهری','۰۹۲۱۴۴۴۴۴۴۴','اصفهان');
مرحله ۴: برای دریافت و نصب این پکیج دستور pip3 install reportlab را در خط فرمان وارد میکنیم که نحوه استفاده از آن در شکل زیر نشان دادهشده است.
مرحله ۵: پکیج reportlab برای اجراشدن، نیاز به پکیج six دارد که نصب آن مشابه مراحلی است که در مرحله ۱ نشان داده شد، با این تفاوت که از دستور pip3 install six استفاده میکنیم.
مرحله ۶: برای اتصال به پایگاه داده MySQL نیز به پکیج PyMySQL نیاز داریم که آن را هم مشابه مراحل قبلی به کمک دستور pip3 install pymysql دریافت و نصب میکنیم.
تا اینجای کار برای تولید یک گزارش که در آن از کاراکترهای فارسی استفادهنشده است، کافی است ولی اگر قصد تولید یک گزارش که در آن از کارترهای غیرفارسی استفادهشده را دارید، میتوانید از مرحله ۸ ادامه آموزش را دنبال کنید.
به دلیل راست به چپ بودن زبان شیرین فارسی، تنها با انجام دادن مراحل بالا و اجرای برنامه، بهعنوانمثال بهجای چاپ شدن عبارت سلام دنیا، با اشکالی نظیرایند مالس و [][][][] [][][][] روبرو خواهیم شد که برای ما خوشایند نیست!
مرحله ۷: برای حل این مشکل، از پکیج utils استفاده خواهیم کرد که آن را بدون استفاده از pip و بهصورت دستی، به دایرکتوری پروژه اضافه میکنیم(پوشه utils را در کنار فایلهای پروژه قرار میدهیم).
مرحله ۸: در این مرحله یک فایل متنی با پسوند .py برای نوشتن کدهای مثال که به زبان پایتون هستند، در مسیر پروژه ایجاد میکنیم. در این مثال، ما فایل report.py را برای این منظور ایجاد کردهایم (که در شکل بالا هم قابلمشاهده است) و در آن مثال را شرح میدهیم.
# -*- coding: utf-8 -*- from reportlab.lib.pagesizes import A4, A5, landscape from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Paragraph, Table, TableStyle from reportlab.lib.units import mm, cm from reportlab.platypus.flowables import PageBreak, Spacer from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib import colors from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from utils.spreadsheettable import SpreadsheetTable from utils import arabic_reshaper from utils.bidi.algorithm import get_display import pymysql
مطابق تکه کد بالا، ابتدا کتابخانههای موردنظر که به ترتیب مربوط به پکیجهای reportlab، utils و pymysql هستند را در برنامه وارد میکنیم.
در ادامه برای واکشی اطلاعات جدول persons از پایگاه داده متد get_data را مطابق تکه کد صفحه بعد، تعریف میکنیم و با استفاده از رشته اتصال به پایگاه داده که شامل ویژگیهای آدرس آیپی، شماره پورت، نام کاربری، کلمه عبور، نام پایگاه داده و کدینگ مورداستفاده برای اتصال هستند به پایگاه داده متصل میشویم و پرسوجوی SELECT * FROM persons را اجرا میکنیم و در یک حلقه for، برای هر ردیف ستونهای موردنیاز از هر آن ردیف را مشخص میکنیم و آن را به لیست persons اضافه و در آخر این لیست را بهعنوان خروجی تابع در نظر میگیریم.
def get_data(): conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='', db='reportlab_db', charset='utf8') cur = conn.cursor() cur.execute("SELECT * FROM persons") result = cur.fetchall() persons = list() for record in result: persons.append([record[0], record[1], record[2], record[3], record[4]]) return persons
در ادامه متد replace_digits را برای تبدیل اعداد انگلیسی به فارسی، مطابق با تکه کد زیر، تعریف میکنیم که بهعنوان ورودی یک عدد انگلیسی را دریافت و معادل فارسی آن را برمیگرداند. که کد آن در زیر نشان دادهشده است.
def replace_digits(num): number = { 0: '۰', 1: '۱', 2: '۲', 3: '۳', 4: '۴', 5: '۵', 6: '۶', 7: '۷', 8: '۸', 9: '۹' } return number.get(num)
به کمک متد utf8_converter رشتههای فارسی را به شکل مناسب برای نمایش درمیآوریم که بهعنوان ورودی یک رشته فارسی را دریافت کرده و آن را بهصورت مرتبشده از خروجی این تابع تحویل میگیریم که کد آن در زیر نشان دادهشده است.
def utf8_converter(text): reshaped_text = arabic_reshaper.reshape(text) bidi_text = get_display(reshaped_text) return bidi_text
متد reporter، مهمترین متد این مثال بوده که وظیفه آن ایجاد و آرایش اجزای داخل صفحات گزارش است. در ادامه به توضیح نحوه کار و ارائه کدهای آن خواهیم پرداخت.
def reporter(): pdfmetrics.registerFont(TTFont('BNazanin', 'D:\\font\\BNazanin.ttf')) style_sheet = getSampleStyleSheet() style_sheet.add(ParagraphStyle(name='TestStyle', fontName='BNazanin', fontSize=25, leading=50, alignment=TA_CENTER)) style_sheet.add(ParagraphStyle(name='PageNum', fontName='BNazanin', fontSize=15, leading=9, alignment=TA_CENTER)) style_sheet.add(ParagraphStyle(name='TTitle', fontName='BNazanin', fontSize=10, alignment=TA_CENTER)) style_sheet.add(ParagraphStyle(name='About', fontSize=6, alignment=TA_RIGHT)) table_style = [ ('GRID', (0, 0), (-1, -1), 1, colors.black), ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('FONTSIZE', (0, 0), (-1, -1), 10), ] data = [] person_id = Paragraph(utf8_converter('ردیف'), style_sheet['TTitle']) first_name = Paragraph(utf8_converter('نام'), style_sheet['TTitle']) last_name = Paragraph(utf8_converter('نام•خانوادگی'), style_sheet['TTitle']) phone_num = Paragraph(utf8_converter('شماره تلفن'), style_sheet['TTitle']) user_address = Paragraph(utf8_converter('آدرس'), style_sheet['TTitle']) data.append([user_address, phone_num, last_name, first_name, person_id]) story = [] title = Paragraph(utf8_converter('گروه فنی مهندسی خورشید ارتباطات'), style_sheet["TestStyle"]) about = Paragraph(utf8_converter('www.KhorshidErtebatat.com'), style_sheet["About"]) story.append(title) page_number = 1 overflow = 0 for person in get_data(): data.append([Paragraph(utf8_converter(person[4]), style_sheet['TTitle']), Paragraph(get_display(person[3]), style_sheet['TTitle']), Paragraph(utf8_converter(person[2]), style_sheet['TTitle']), Paragraph(utf8_converter(person[1]), style_sheet['TTitle']), Paragraph(str(replace_digits(person[0])), style_sheet['TTitle']) ]) overflow += 1 if overflow == 30: story.append(Spacer(0, 5 * mm)) spreadsheet_table = SpreadsheetTable(data, repeatRows=1, colWidths=( 3 * cm, 28 * mm, 25 * mm, 18 * mm, 13 * mm)) spreadsheet_table.setStyle(table_style) story.append(spreadsheet_table) story.append(Spacer(0, 5 * mm)) story.append(Paragraph( utf8_converter(str(replace_digits(page_number))) + utf8_converter('صفحه شماره '), style_sheet['PageNum'])) story.append(about) story.append(PageBreak()) data = [[user_address, phone_num, last_name, first_name, person_id]] overflow = 0 page_number += 1 story.append(Spacer(0, 5 * mm)) spreadsheet_table = SpreadsheetTable(data, repeatRows=1, colWidths=( 5 * cm, 3 * cm, 4 * cm, 3 * cm, 2 * cm)) spreadsheet_table.setStyle(table_style) story.append(spreadsheet_table) story.append(Spacer(0, 5 * mm)) story.append(Paragraph( utf8_converter(str(replace_digits(page_number))) + utf8_converter('صفحه شماره '), style_sheet['PageNum'])) story.append(about) create_pdfdoc('report.pdf', story)
در این متد در ابتدا قلم(فونت) مورداستفاده را با واردکردن نام و آدرس، معرفی و در ادامه آرایشهایی را برای قسمتهای مختلف صفحات، به کمک مشخص کردن نام، سایز قلم و تنظیماتی نظیر راستچین یا چپچین بودن تعریف میکنیم. پسازآن مشخصات ظاهری جدولی که برای نمایش اطلاعات از آن استفاده میکنیم را مشخص کرده و در ادامه آرایش نوشتههایی نظیر سربرگ و همچنین عنوان فیلدهای جدول را به همراه آرایش آنها تعریف کرده و به آرایه data اضافه میکنیم. همچنین کلیه نوشتهها باید صورت یک Paragraph مشخص شوند که شامل متن و آرایش آن است.
نکته ۱: در این کد از دو آرایه data و story استفادهشده است. کلیه نوشتههای قابلچاپ به همراه آرایشهای آنها در آرایه story ذخیرهشده و درنهایت محتویات این فایل بهصورت PDF درخواهند آمد. برای سهولت در آرایش و نمایش اطلاعاتی که فرم جدول دارند از پکیج SpreadSheetTable استفاده میکنیم، یعنی اطلاعات جدول را ابتدا به آرایه data اضافه میکنیم و آن را توسط پکیج SpreadSheetTable آماده کرده و به آرایه storyy اضافه میکنیم.
نکته ۲: از متغیر overflow برای کنترل اینکه چه تعداد سطر در یک صفحه قرار میگیرند، استفاده میشود.(در این آموزش، در صفحه ۳۰ سطر، قرار خواهد گرفت.)
در آخر از متد create_pdfdoc که در زیر آورده شدهاست، برای تولید فایل نهایی PDF استفاده و آرایه story، آدرس و نام فایل خروجی را بهعنوان ورودی آن، وارد میکنیم. در ادامه، در این متد تنظیمات کلی صفحه انجام و درنهایت فایل PDF خروجی قابلاستفاده ساخته میشود.
def create_pdfdoc(pdfdoc, story): MARGIN_SIZE = 5 * mm PAGE_SIZE = A4 pdf_doc = BaseDocTemplate(pdfdoc, pagesize=PAGE_SIZE, leftMargin=MARGIN_SIZE, rightMargin=MARGIN_SIZE, topMargin=MARGIN_SIZE, bottomMargin=MARGIN_SIZE) main_frame = Frame(MARGIN_SIZE, MARGIN_SIZE, PAGE_SIZE[0] - 2 * MARGIN_SIZE, PAGE_SIZE[1] - 2 * MARGIN_SIZE, leftPadding=0, rightPadding=0, bottomPadding=0, topPadding=0, id='main_frame') main_template = PageTemplate(id='main_template', frames=[main_frame]) pdf_doc.addPageTemplates([main_template]) pdf_doc.build(story) reporter() print("Your report is ready.")
موفق و پیروز باشید.
به شدت پست ارزشمندی هست. ممنون از شما
خواهش میکنم. خوشحالم که مفید بوده براتون.
با سلام ممنون از کدتون ولی ی خطا میده ممنون میشم راهنمایی کنید
line 13 از فایل spreadsheet
def spanFixDim(V0, V, spanCons, FUZZ=rl_config._FUZZ):
NameError: name ‘rl_config’ is not defined
سلام. ممنون بابت نظرتون.
راستش این کد خیلی قدیمی هست و زمانی که من نوشتمش مشکلی نداشت. احتمالا پکیجها آپدیت شدن و تغییراتی داشتن که درحالحاضر و متاسفانه من زمان بررسی کردنش رو ندارم.
ممنون میشم اگر شما موفق شدید مشکلش رو حل کنید بهم اطلاع بدید که اینجا هم اصلاحش کنم.