📜 ⬆️ ⬇️

The second part of the comparison of python and tcl

In the previous article , the implementation of the task of collecting data on calls from the PBX was described, including receiving, parsing data and adding to the database. The solution to this problem is presented in two programming languages ​​- python and tcl. And here, as promised, I present the code (in two languages) of the WEB interface, which includes a telephone directory and call reports.

Apache2 with wsgi and rivet modules is used as a web server (respectively for python and tcl). All packages were installed from the official repositories, in the absence of reps in the reps, the rpm-packages were assembled independently. The basic system is chosen simply Linux.

Code examples will be given in this order: first python and then tickle. The names of the procedures and variables tried to do the same. In some places, the html-code was simply copied, and since the project is quite small, I didn’t separate the presentation (html) from the logic (python, tcl).

And the last remark - in the implementation on python, work with LDAP (AD domain) is included. in prom. the telephone directory is loaded from the domain; in the ticker implementation, this is not the case, due to my lack of AD at the moment.
')
Let's get started

main - the main procedure called at the beginning of the script.
Depending on which link (menu item) we “click” on the page in main, the corresponding parameter will be highlighted and a response (html-code) will be generated by calling the appropriate procedures.

The URL looks like this - “ localhost /? Query_type = external ”. query_type can be: internal - directory of internal phones, external - list of external lines, report - call report, ldap - directory of AD.

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] 

In both cases, the values ​​of the parameters are taken from the array of variables: in python it is environ ['QUERY_STRING'] in the tickle rivet :: var.

 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 } 

Those. to make it clearer:

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

this means that the value of the $ output variable will include the result of executing the procedures: header, body, reportForm, reportData, and footer .

header - returns the page header. css is used the same, it looks the same.

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 } 


Home page:

image

footer - the procedure for displaying the "bottom" of the page

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

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


The main "body" of the page
 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 - menu output:

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 } 

Now let's see how the directory of internal numbers looks like (a telephone directory) from the outside:

image

and inside, for this (in the part concerning the listing of numbers) the getInternalNumbers procedure is responsible :

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" } 


To work with the DBMS for python, pymysql was taken, for the tickle mysqltcl . What is there, that there is a connection to the database written in one line:

 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] } 

Listing external lines is similar to the directory of internal numbers and I will not give it here. Let's look in more detail at the report on calls, for the sake of what, actually, all this and was started. So it looks visually:

image

The report is divided into two procedures - the withdrawal of the form and the request and output of the data.
The form does not represent anything special, generated by the ReportForm procedure:

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 } 


In the ReportData procedure, validation of parameter input (number and date) is included, i.e a kind of protection from SQL injection and inattention of users, a query to the database and data output to the screen. Of course, it could have been broken even smaller, but I did not.

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 } 

There are some differences in the date conversion algorithm. Since the mysql date (in my case) is stored in the form of YYYY-MM-DD, and for us this format is inconvenient, then the date was converted from one format to another.

For python, the built-in functions were used:

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

But for tcl, I acted differently. Due to the fact that checking for correct date entry is performed by 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]} 

Then at the output of this command, the date is already broken up into its component parts and it simply needs to be put together as required:

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

And also in the implementation on the tcl line:

 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]) 

replaced by a cycle:

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

That's all.

Finally, even though the procedure for obtaining data on LDAP is beyond the scope of the article’s topic but is included in the project, it will be fair to show it as well:

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") 


All files are available on BitBucket . At this point, I stop the story on the comparison of python and tcl, in any case, until the material is gathered.

Source: https://habr.com/ru/post/348222/


All Articles