pythonとtclを比較する第2部

前回の記事では、受信、データの分析、データベースへの追加など、PBXからの呼び出しに関するデータを収集するタスクの実装について説明しました。 この問題の解決策は、Pythonとtclという2つのプログラミング言語で提供されています。 そして今、約束どおり、電話帳と通話レポートを含むWEBインターフェイスのコード(2言語)を紹介します。



Apache 2とwsgiおよびリベットモジュール(それぞれpythonとtcl用)がWebサーバーとして使用されます。 すべてのパッケージは公式リポジトリからインストールされ、カブにrpmパッケージがない場合は、それらは独立して組み立てられました。 基本システムは単にLinuxを選択します。



コード例は次の順序で提供されます:最初にpython、次にくすぐります。 プロシージャと変数の名前を同じにしようとしました。 一部の場所では、htmlコードが単純にコピーされ、プロジェクトが非常に小さいため、プレゼンテーション(html)をロジック(python、tcl)から分離し始めませんでした。



最後の注意-Pythonでの実装では、LDAP(ADドメイン)での作業が含まれています。 プロムで。 電話帳は動作中のドメインからロードされますが、現時点ではADが不足しているため、これは実装のくすぐりには当てはまりません。



始めましょう。



main-スクリプトの開始時に呼び出されるメインプロシージャ。

mainのページで「突く」リンク(メニュー項目)に応じて対応するパラメーターが強調表示され、適切なプロシージャを呼び出して応答(htmlコード)が生成されます。



URLは次のようになります-" localhost /?Query_type = external "。 query_typeは次の値を取ることができます: internal-内部電話のディレクトリ、 external-外部回線のリスト、report-呼び出しに関するレポート、 ldap -HELLからのディレクトリ。



def application(environ, start_response): status = '200 OK' #       -  if environ['QUERY_STRING'] == '': output = bytes((header() + body() + footer()).encode('utf8')) elif environ['QUERY_STRING'].split('&'): paramDict = {item.split('=')[0]: item.split('=')[1] for item in environ['QUERY_STRING'].split('&')} if paramDict.get('query_type') == 'internal': output = bytes((header() + body() + getInternalNumbers() + footer()).encode('utf8')) elif paramDict.get('query_type') == 'external': output = bytes((header() + body() + getCOline() + footer()).encode('utf8')) elif paramDict.get('query_type') == 'report': output = bytes((header() + body() + ReportForm() + ReportData(environ) + footer()).encode('utf8')) elif paramDict.get('query_type') == 'ldap': domain = paramDict.get('domain') group = paramDict.get('group') output = bytes((header() + body() + getLDAPusers(domain, group) + footer()).encode('utf8')) else: output = bytes((header() + body() + footer()).encode('utf8')) response_headers = [('Content-type', 'text/html;charset=utf-8'), ('Content-Length', str(len(output)))] start_response(status, response_headers) return [output]
      
      





どちらの場合も、パラメーター値は変数の配列から取得されます。Pythonでは、これはリベット:: varのenviron ['QUERY_STRING']です。



 proc main {} { if { [::rivet::var exists query_type] } { set query_type [::rivet::var get query_type] if {$query_type == "internal"} { set output "[header] [body] [getInternalNumbers] [footer]" } elseif {$query_type == "external"} { set output "[header] [body] [getCOline] [footer]" } elseif {$query_type == "ldap"} { set output "[header] [body] [getLDAPusers] [footer]" } elseif {$query_type == "report"} { set output "[header] [body] [reportForm] [reportData] [footer]" } } else { set output "[header] [body] [footer]" } return $output }
      
      





すなわち より明確にするために:



 set output "[header] [body] [reportForm] [reportData] [footer]"
      
      





これは、 $ output変数の値に、 ヘッダー、本体、reportForm、reportData 、およびフッターの実行結果が含まれることを意味します。



header-ページのタイトルを返します。 cssは同じように使用され、すべてが同じに見えます。



Python + Tcl
 def header(): txtHeader = '<html><head><meta charset="utf-8"><link rel="shortcut icon" href="favicon.png" >\n' \ '<link rel="icon" type="image/png" href="favicon.png" >\n' \ '<link href="css/layout.css" rel="stylesheet" type="text/css" />\n' \ '<title></title>\n' \ '<link href="css/menu.css" rel="stylesheet" type="text/css" /></head><body>\n' return txtHeader
      
      





 proc header {} { set txtHeader "<html><head><meta charset=\"utf-8\"><link rel=\"shortcut icon\" href=\"favicon.png\" >\n <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\" >\n <link href=\"css/layout.css\" rel=\"stylesheet\" type=\"text/css\" />\n <title></title>\n <link href=\"css/menu.css\" rel=\"stylesheet\" type=\"text/css\" /></head><body>\n" return $txtHeader }
      
      







開始ページ:



画像



フッター -ページの下部を表示する手順



Python + Tcl
 def footer(): txtFooter = '</body></html>\n' return txtFooter
      
      





 proc footer {} { set txtFooter "</body></html>\n" return $txtFooter }
      
      







ページのメインの「本文」
 def body(): <spoiler title="Python + Tcl"> txtBody = '<table><tr><td><h2 align=left></h2></td></tr>\n' \ '<tr><td>{}</td></tr></table>\n'.format(menu()) return txtBody
      
      





 proc body {} { set txtBody "<table><tr><td><h2 align=left></h2></td></tr>\n <tr><td>[menu]</td></tr></table>\n" return $txtBody }
      
      





menu-メニュー出力:



Python
 def menu(): #<div class="container"> txtMenu = '<div class="container"><ul id="nav"></li>\n' \ '<li><a class="hsubs" href="#"></a><ul class="subs">\n' \ '<li><a href="?query_type=external"> </a></li>\n' \ '<li><a href="?query_type=ldap&domain=domain1">  1</a></li>\n' \ '<li><a href="?query_type=ldap&domain=domain2">  2</a></li>\n' \ '<li><a href="?query_type=ldap&domain=domain3">  3</a></li></ul></li>\n' \ '<li><a class="hsubs" href=""></a><ul class="subs">' \ '<li><a href="?query_type=report&report_type=int">   </a></li>\n' \ '<li><a href="?query_type=report&report_type=dep">  </a></li>\n' \ '</ul></div>\n' return txtMenu
      
      





Tcl
 proc menu {} { set txtMenu "<div class=\"container\"><ul id=\"nav\"></li> <li><a class=\"hsubs\" href=\"#\"></a><ul class=\"subs\"> <li><a href=\"?query_type=external\"> </a></li> <li><a href=\"?query_type=internal\"></a></li></li> <li><a href=\"?query_type=ldap\"> (AD)</a></li></ul></li> <li><a class=\"hsubs\" href=\"\"></a><ul class=\"subs\"> <li><a href=\"?query_type=report&report_type=int\">   </a></li> <li><a href=\"?query_type=report&report_type=dep\">  </a></li>\n</ul></div>" return $txtMenu }
      
      





次に、内部番号のディレクトリ(電話帳)が外部からどのように見えるかを見てみましょう。



画像



内部では、 getInternalNumbersプロシージャがこれを担当します(数値のリストの出力に関して)。



Python
 def getInternalNumbers(): conn = connectDB() c = conn.cursor() order = 'int_number' result = '<h4 align=left>  </h4>' qwr = "SELECT * FROM int_number ORDER by " + order c.execute(qwr) listHeader = '<table class="table_dark"><tr><th>№ /</th>\n' \ '<th><a href=index.py?query_type=internal&order=int_number></a></th>\n' \ '<th><a href=index.py?query_type=internal&order=fio></a></th>\n' \ '<th><th>-</th></tr>\n' result = result + listHeader for row in c.fetchall(): rowData = "<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (row[0], row[1], row[2], row[4], row[3]) result = result + rowData return (result + "</table>\n")
      
      







Tcl
 proc getInternalNumbers {} { set conn [connectDB] set order "int_number" set result "<h4 align=left>  </h4>" set qr "SELECT * FROM int_number ORDER by $order" set query [mysql::query $conn $qr] set listHeader "<table class=\"table_dark\"><tr><th>№ /</th>\n <th><a href=index.py?query_type=internal&order=int_number></a></th>\n <th><a href=index.py?query_type=internal&order=fio></a></th>\n <th><th>.</th></tr>\n" set result "$result $listHeader" set rowData "" while {[set row [mysql::fetch $query]]!=""} { append rowData "<tr><td>[lindex $row 0]</td> <td>[lindex $row 1]</td> <td>[lindex $row 2]</td> <td>[lindex $row 4]</td> <td><a href=\"mailto:[lindex $row 3]\">[lindex $row 3]</a></td></tr>\n" } mysql::endquery $query set result "$result $rowData" disconnectDB $conn return "$result </table>\n" }
      
      







Python用のDBMSを使用するために、mysqltcl tickleでpymysqlが使用されました。 そこにあるもの、そこにあるもの、データベースへの接続は1行で記述されます。



 def connectDB(): c = pymysql.connect( db='ats', user='ats', passwd='pass', host='xxxx', charset='utf8') return c
      
      





 proc connectDB {} { return [mysql::connect -host localhost -user root -db ats] }
      
      





外部回線のリストの出力は内部番号のディレクトリに似ているため、ここでは説明しません。 通話に関するレポートを詳しく見てみましょう。実際、これらはすべて考えられたものです。 だから視覚的に見える:



画像



レポートは2つの手順に分かれています-これは、フォームの出力と、受信データの要求と出力です。

フォームは、ReportFormプロシージャによって生成された特別なものを表しません。



Python
 def ReportForm(): txtReportForm = '<h3 align=center>  </h3><table><tr valign=top><td>' \ '<form><input type=hidden name="query_type" value="report">' \ '<fieldset class="report">' \ '<label><input type="text" name="int_number"></label>' \ '<label> <input type="date" name="date_begin"></label>' \ '<label><input type="date" name="date_end"></label>' \ '</fieldset>' \ '<fieldset class="report-action">' \ '<input class="btn" type="submit" name="submit" value="">' \ '</fieldset></form></td>' return txtReportForm
      
      







Tcl
 proc reportForm {} { set txtReportForm "<h3 align=center>  </h3><table><tr valign=top><td> <form><input type=hidden name=\"query_type\" value=\"report\"> <fieldset class=\"report\"> <label><input type=\"text\" name=\"int_number\"></label> <label> <input type=\"date\" name=\"date_begin\"></label> <label><input type=\"date\" name=\"date_end\"></label> </fieldset> <fieldset class=\"report-action\"> <input class=\"btn\" type=\"submit\" name=\"submit\" value=\"\"> </fieldset></form></td>" return $txtReportForm }
      
      







ReportDataプロシージャには、パラメーター入力(数値と日付)の正確性、つまり SQLインジェクションとユーザーの不注意に対する一種の保護。データベースにクエリを実行し、画面にデータを表示します。 もちろん、さらに小さく粉砕することもできましたが、私はしませんでした。



Python
 def ReportData(environ): paramDict = {item.split('=')[0]: item.split('=')[1] for item in environ['QUERY_STRING'].split('&')} #    #   (3  100-999) templateNumber = '(^[1-9][0-9][0-9]$)' if re.match(templateNumber, str(paramDict.get('int_number'))) is not None: numbers = paramDict.get('int_number') else: return ErrorMessage('  ') #          templateDate = '^(0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20)[0-9][0-9]$' if re.match(templateDate, str(paramDict.get('date_begin'))) is not None: date_begin = time.strftime("%Y-%m-%d", time.strptime(str(paramDict.get('date_begin')), "%d.%m.%Y")) else: return ErrorMessage('    ') if re.match(templateDate, paramDict.get('date_end')) is not None: date_end = time.strftime("%Y-%m-%d", time.strptime(str(paramDict.get('date_end')), "%d.%m.%Y")) else: return ErrorMessage('    ') conn = connectDB() c = conn.cursor() order = 'call_date' result = '<td><h4 align=left>    {}</h4>'.format(numbers) qwr = "SELECT * FROM cdr where int_number=\'{}\' AND call_date BETWEEN CAST(\'{}\' AS DATE) AND CAST(\'{}\' AS DATE) LIMIT 1000".format(numbers, date_begin, date_end) c.execute(qwr) listHeader = '<table class="table_dark"><tr>' \ '<th>№ /</th><th></th><th></th><th>.</th><th>.</th><th> </th>' \ '<th>Ring</th><th></th><th></th><th> </th><th></th><tr>\n' result = result + listHeader for row in c.fetchall(): rowData = '<td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>'\ .format(row[0], row[1], row[2], row[3], row[4], row[5], row[5], row[7], row[9], row[9], row[10]) result = result + rowData return (result + "</table></td></tr></table>\n" + qwr)
      
      





Tcl
 proc reportData {} { set result "" set err "" #            if { [::rivet::var exists int_number] } { set number [::rivet::var get int_number] if {[regexp -nocase -all -- {^([0-9][0-9][0-9])$} $number match v1]} { append result $number } else { append err "<p><font color=red></b>  </b></font></p>" } } else { return } if { [::rivet::var exists date_begin] } { if [regexp -nocase -- {^(0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20[0-9][0-9])$} [::rivet::var get date_begin] match dmy] { set dBegin "$y-$m-$d" } else { append err "<p><font color=red></b>  </b></font></p>" } } if { [::rivet::var exists date_end] } { if [regexp -nocase -- {^(0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20[0-9][0-9])$} [::rivet::var get date_end] match dmy] { set dEnd "$y-$m-$d" } else { append err "<p><font color=red></b>  </b></font></p>" } } if {$err ne ""} { return "<td>$err</td></tr></table>" } set conn [connectDB] set order "call_date" set result "<td><h4 align=left>    $number</h4>" set qr "SELECT * FROM cdr where int_number=\'$number\' AND call_date BETWEEN CAST(\'$dBegin\' AS DATE) AND CAST(\'$dEnd\' AS DATE) LIMIT 1000" set query [mysql::query $conn $qr] set listHeader "<table class=\"table_dark\"><tr> <th>№ /</th><th></th><th></th><th>.</th><th>.</th><th> </th> <th>Ring</th><th></th><th></th><th> </th><th></th><tr>" append result $listHeader set rowData "" while {[set row [mysql::fetch $query]]!=""} { append rowData "<tr>" for {set i 0} {$i <=10} {incr i} { append rowData "<td>[lindex $row $i]</td>" } } append result "$rowData </table></td></tr></table>" return $result }
      
      





日付変換アルゴリズムにはいくつかの違いがあります。 mysqlでは日付(私の場合)はYYYY-MM-DDの形式で保存されるため、この形式は不便なので、日付をある形式から別の形式に変換しました。



Pythonでは、組み込み関数が使用されました。



 date_begin = time.strftime("%Y-%m-%d", time.strptime(str(paramDict.get('date_begin')), "%d.%m.%Y"))
      
      





しかし、tclの場合、私は異なる行動をとりました。 正しい日付入力のチェックはregexpによって実行されるという事実のため:



 if { [regexp -nocase -- {^(0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20[0-9][0-9])$} [::rivet::var get date_begin] match dmy]}
      
      





次に、このコマンドの出力で、日付は既にそのコンポーネント部分に分割されており、必要に応じて配置する必要があります。



 set dBegin "$y-$m-$d"
      
      





そして、tcl行の実装では:



 rowData = '<td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>'\ .format(row[0], row[1], row[2], row[3], row[4], row[5], row[5], row[7], row[9], row[9], row[10])
      
      





ループに置き換えられました:



 for {set i 0} {$i <=10} {incr i} { append rowData "<td>[lindex $row $i]</td>" }
      
      





以上です。



最後に、LDAPでデータを取得する手順は記事のトピックの範囲を超えていますが、プロジェクトの一部ですが、それを示すことは公平です。



LDAP Python
 #   (, , , )  LDAP (AD) def getLDAPusers(domain, group): order = 'int_number' if domain == 'domain1': head = '<p>     xxxxxx +  </p>' org = '1' s = Server('192.168.1.1') c = Connection(s, user='domain1\\phone', password="pass", authentication=NTLM) # perform the Bind operation if not c.bind(): print('error in bind', c.result) c.search('OU=users,dc=domain1,dc=local', '(objectclass=person)', attributes=['cn', 'mail', 'telephoneNumber', 'department', 'title', 'mobile']) elif domain == 'domain2': head = '<p>     xxxxxxx +  </p>' org = '2' s = Server('192.168.2.2') c = Connection(s, user='domain2\\phone', password="pass", authentication=NTLM) if not c.bind(): print('error in bind', c.result) c.search('OU=users,dc=domain2,dc=local', '(objectclass=person)', attributes=['cn', 'mail', 'telephoneNumber', 'department', 'title', 'mobile']) elif domain == 'domain3': head = '<p>     xxxxxxx +  </p>' org = '3' s = Server('192.168.3.3') c = Connection(s, user='domain3\\phone', password="pass", authentication=NTLM) if not c.bind(): print('error in bind', c.result) c.search('OU=users,dc=domain3,dc=local', '(objectclass=person)', attributes=['cn', 'mail', 'telephoneNumber', 'department', 'title', 'mobile']) result = '<h4 align=left>  %s</h4>\n%s' % (org, head) listHeader = '<table class="table_dark"><tr>\n' \ '<th></th>\n' \ '<th></th><th></th>\n' \ '<th></th>\n' \ '<th>.</th><th> </th></tr>\n' result = result + listHeader for item in c.entries: item = str(item) name = re.search('(cn:)(.+?)(\n)', item) if name: name = name.groups()[1] else: name=name department = re.search("(department:)(.+?)(\n)", item) if department: dep = department.groups()[1] else: dep = "" title = re.search("(title:)(.+?)(\n)", item) if title: title = title.groups()[1] else: title = "" mail = re.search("(mail:)(.+?)(\n)", item) if mail: mail = mail.groups()[1] else: mail = "" telephone = re.search("(telephoneNumber:)(.+?)(\n)", item) if telephone: phone = telephone.groups()[1] else: phone = "" mobile = re.search("(mobile:)(.+?)(\n)", item) if mobile: mobile = mobile.groups()[1] else: mobile = "" rowData = "<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td><a href=\"mailto:%s\">%s</a></td><td>%s</td></tr>\n" % (name, dep, title, phone, mail, mail, mobile) result = result + rowData return (result + "</table>\n")
      
      







すべてのファイルはBitBucketで利用できます。 これについては、少なくとも資料が入力されるまで、pythonとtclの比較について話すのをやめます。



All Articles