Море приколов и флешек на CD Море разнообразных приколов! :: KOLBAS2003.NAROD.RU

Главная Статьи Книги Опрос Чат Гостевая книга Форум Ссылки

 Переползаем на Питон

Осенью прошлого года был запущен сайт http://www.iso.ru/, разработанный компанией ADT. "Движок" сайта, представляющий собой набор CGI скриптов, был написан на Perl. По прошествии полугода эксплуатации возникла необходимость расширить функциональность сайта. Поэтому встал вопрос о выборе языка для написания скриптов.

Perl хорошо подходит для обработки текстов и широко используется для web-программирования, однако программы, написанные на Perl, трудночитаемы и неудобны для сопровождение из-за специфического синтаксиса Perl'a. Если стоит задача быстро написать небольшой скрипт усилиями одного человека и у вас специфический склад мышления, то, возможно, Perl - это то, что вам нужно. Если же требуется разработать достаточно сложную систему и затем организовать ее поддержку коллективом специалистов, то для этих целей, на мой взгляд, более подходит Python.

Python сочетает в себе понятный синтаксис и мощь, имеет развитые средства обработки текста и создания web-приложений. Python доступен для различных операционных систем, таких как UNIX (Linux), MacOS, MS-Windows 3.1, Windows NT, OS/2 и даже MS-DOS. Скрипты, написаные на Python являются хорошо переносимыми между платформами. Если бы возникла задача перенести сайт http://www.iso.ru/ с платформы Linux на Windows NT, потребовались бы минимальные изменения кода (по существу, пришлось бы только исправить пути к файлам шаблонов). Впрочем, более подробно о достоинствах Python'а вы можете узнать из статьи "Знакомьтесь - Python" Якова Марковича, опубликованной в Журнале №1.

Таким образом, решено было использовать Python для написания скриптов для сайта http://www.iso.ru/. "Движок" сайта состоял из следующих логических частей (скриптов):

  • Главная страница - вывод главной страницы сайта, списка новостей и событий.
  • Новости - вывод текста новости, списка архивных новостей сайта, клуба, технологий.
  • События - вывод текста события.
  • Регистрация - осуществление процедуры регистрации посетителя: запись в базу данных информации о посетителе, контроль уникальности учетных записей.
  • Работа с посетителями - проверка входного имени и пароля, организация скачивания файлов с сайта, подписка на новости, доступ к страницам технической поддержки.
  • Формоотправитель - скрипт, занимающийся отправкой заполненых посетителем форм на e-mail поддержки сайта.
  • Журнал - работа с базой данных журнала статей по IT-технологиям.
  • Гостевая книга - запись и просмотр комментариев к статьям журнала.
  • Конференции
  • Административный интерфейс - редактирование таблиц базы данных сайта, загрузка новых статей, новостей, событий, выгрузка данных из базы в формат CSV, управление конференциями.

В первую очередь было решено расширить функциональность журнала -добавить систему оценки статей и сервис выбора самой интересной в данном номере. Таким образом, в список потребовалось добавить еще один скрипт. Все остальные скрипты также нужно было в большей или меньшей степени усовершенствовать.

Все скрипты написаны достаточно стандартным образом: создается экземпляр класса FieldStorage, который читает содержимое формы, затем, в зависимости от наличия и содержания определенных ключей организуется ветвление, обработка данных и вывод результата. В этой статье мне хотелось бы только поделиться опытом преодоления некоторых трудностей, возникших при разработке скриптов для http://www.iso.ru/.


Формирование HTML из шаблонов

Как известно, вывод любой CGI программы состоит из двух частей: заголовка и данных, которые разделяются пустой строкой. Сначала программа должна сообщить клиенту, какой тип данных он будет получать. Это достигается печатью набора HTTP заголовков в стандартный вывод. Например, строка

print 'Content-Type: text/html\n'

сообщает браузеру, что он будет получать стандартный HTML.

Затем, после разделительной строки, идут произвольные данные (обычно, код HTML). Конечно, можно просто вывести HTML текст оператором print, вставленным в тело программы, но это подходит лишь для небольших фрагментов HTML кода. Когда же необходимо вывести несколько десятков строчек, исходный текст программы становиться очень большим и нечитаемым, к тому же иногда необходимо использовать одни и те же фрагменты кода с незначительными изменениями. В этом случае удобно использовать шаблоны.

Шаблон представляет собой текстовый файл, содержащий HTML код с переменными, которые впоследствии будут заменяться необходимыми значениями. Имена переменных желательно сделать такими, чтобы исключить их случайное повторение внутри HTML кода (например, не нужно использовать переменную с именем table, так как потом в результате пострадают все определения таблиц). Для переменных в своих шаблонах я использую следующее соглашение: имя переменной начинается и заканчивается символом $ (например, $var_name$). Это исключает возмоожность совпадения с тегами HTML и словами в тексте документа.

Вот пример шаблона гостевой книги, хранящийся в файле guestbook.tmpl:


<table width=100% align=center>
  <tr>
    <td width=100%>
      <table width=100% align=center>
        <tr>
          <td><b>$date$</b> $time$ $author$</td>
        </tr>
      </table>
      <table width =100%>
        <tr>
          <td>
            <div align="justify"><p>$message$<br><br>
            <table>
              <tr>
                <td bgcolor="#074473">
                   <img src="/img/gif.gif" width=200 height=1>
                </td>
              </tr>
            </table>
          </td>
        </tr>
      </table>
      <img src="/img/gif.gif" width="1" height="5" alt="" border="0">
    </td>
    <td><img src="/img/gif.gif" width="30" height="1" alt="" border="0"></td>
  </tr>
</table>

Для удобства работы можно написать функцию, читающую шаблон и заменяющую все переменные в нем на нужные значения. Она получает в качестве аргументов имя шаблона и словарь, представляющий собой набор пар <имя переменной> : <значение>. Возвращает функция сконструированный текст. Функция может выгладеть примерно так:


def replace_tmpl( tmpl, var_list ):
    lines = open( tmpl ).readlines()
    src = "%s"*len(lines) % tuple(lines)
    for key, var in var_list.items():
        src = string.replace( src, key, var )
    return src

А вот скрипт, выводящий запись в гостевой книге:


print 'Content-Type: text/html\n\n'
guestbook = '/usr/local/apache/cgi-bin/templates/guestbook.tmpl'
body = replace_tmpl(guestbook, {'$date$'    : '2001-06-07',
                                '$author$'  : 'Артемов Олег',                                 '$message$' : 'Очень полезная статья'}) print body

Все динамические странички на сайте http://www.iso.ru/ формируются таким способом.


Работа с сервером баз данных MySQL

Практически любой сайт, содержащий элементы взаимодействия с пользователем использует какие-либо базы данных. Сайт http://www.iso.ru/ не является исключением. В базе данных хранятся новости, события, статьи журнала, информация по зарегистрированным пользователям и многое другое. Мы используем сервер баз данных MySQL.

На данный момент MySQL является наиболее популярной платформой для создания web-приложений, так как это простой и в тоже время довольно мощный и надежный SQL сервер. MySQL как и Python поддерживает широчайший спектр платформ, включая Linux и Windows NT. Для работы с MySQL в Python используется библиотека MySQLdb, существующая как для Linux, так и для Win32.

Работа с базой данных проходит достаточно стандартно. Сначала создается объект, устанавливающий соединение с БД (Connection Object):


mydb=MySQLdb.Connect(db='iso',host='localhost',
                     user='root',unix_socket='/tmp/mysql.sock')


Затем создается объект-курсор:


cursor = mydb.cursor()

После этого можно выполнять любые SQL запросы к базе данных:


cursor.execute('SELECT * FROM guestbook ORDER by date DESC')

Далее получаем результат запроса:


resultset = cursor.fetchall()

Метод fetchall возвращает кортеж записей, состоящих из кортежей полей, которые можно перебрать в цикле. Приведенная ниже программа выбирает из базы гостевой книги все сообщения автора "Иванов", подставляет их в шаблон и передает браузеру.


print 'Content-Type: text/html\n\n'
guestbook = '/usr/local/apache/cgi-bin/templates/guestbook.tmpl'
mydb=MySQLdb.Connect(db='iso',host='localhost',
                     user='root',unix_socket='/tmp/mysql.sock')
cursor.execute('SELECT date, author, massage FROM guestbook WHERE author="Иванов"')
resultset = cursor.fetchall()
body = ''
for row in resultset:
    body = body+replace_tmpl(guestbook, {'$date$' : row[0].strftime("%d-%m-%Y"),                                             '$author$' : str(row[1]),
                                            '$message$' : str(row[2]))
print body


Отправка форм

Часто возникает задача передачи данных от пользователя на сайт (пожелания по работе сайта, материалы, которые пользователю хотелось бы разместить на сайте и т.д). В простейшем случае - это текстовое сообщение, набираемое в форме и посылаемое затем на электронный адрес поддержки сайта. В этом случае форма может выглядеть следующим образом:


<form action="/cgi-bin/forms.cgi" METHOD="GET">
  Организация: <input type="Text" size="20" name="org">
  Ваше имя: <input type="Text" size="20" name="name">
  Ваш e-mail: <input type="Text" size="20" name="email">
  Тема: <input type="Text" size="20" name="tema">
  Сообщение: <textarea cols="20" rows="6" name="message"></textarea>
  <input type="reset" value="Очистить">$nbsp; $nbsp; $nbsp; $nbsp;
  <input type="submit" value="Отправить">
</form>

Для отправки сообщений по протоколу SMTP нужно использовать библиотеку smtplib. Определяя экземпляр класса SMTP, устанавливаем соединение с SMTP сервером:


import smtplib

mail=smtplib.SMTP("smtpserver.ru")

Формируем тело сообщения из полученных данных в соответствии с RFC822:


form = cgi.FieldStorage()
keys = {}
for k in form.keys():
    keys[k] = form[k].value
msg = """Subject: Новость\n                #тема сообщения
         From: Intersoft Web Server \n    #отправитель
         MIME-Version: 1.0\n              #версия MIME
         Content-Type: text/html\n\n      #тип сообщения
         <br>Имя: %s                      #тело сообщения
         <br>E-mail: %s
         <br>Тема: %s  
         <br>Сообщение: %s""" % \
(keys['name'], keys['email'], keys['tema'], keys['message'])

Затем вызываем метод sendmail для отсылки сообщения:


mail.sendmail( 'admin@iso.ru', 'market@iso.ru', msg )

Здесь admin@iso.ru - адрес отправителя, market@iso.ru - адрес получателя. Можно реализовать и более сложную функциональность с пересылкой вложенных файлов. Для начала в форму нужно добавить поле ввода file:


<input name="attach" type="file" size="12">

Для создания почтового сообщения с вложениями удобно пользоваться классом MimeWriter, определенным в модуле MimeWriter. Чтобы избежать загромождения тела программы, можно написать функцию, получающую на входе текст сообщения (text), содержание поля file формы (file), имя файла (name), тему письма (subj), адрес получателя (address) и отправляющую по этому адресу письмо с вложением. Отправителем в данном случае всегда является admin@iso.ru.

Функция представляет файл в кодировке base64 и конструирует многокомпонентный документ MIME, который отправляет по адресу address.

import mimetools, MimeWriter, StringIO, smtplib, cgi, os
#задаем каталог для временных файлов
temp_dir = '/tmp/'

def send_attach(text, file, name, subj, address):
    #полное имя временного файла
    tmp_file = temp_dir + name + '.txt'
    #имя загружаемого файла
    src_file = temp_dir + name
    #принимаем загружаемый файл
    infile = open(src_file, 'wb')
    #и записываем его в каталог для временных файлов
    infile.write(file)
    infile.close()
    #создаем экземпляр класса MimeWriter
    outfile = open(tmp_file, 'wb')
    mw = MimeWriter.MimeWriter(outfile)
    #создаем объект для записи многокомпонентного сообщения
    mw.startmultipartbody("mixed")
    #записываем заголовки
    mw.flushheaders()
    #создаем часть сообщения типа text/html и записываем туда text
    subpart = mw.nextpart()
    pout = subpart.startbody("text/html", [])
    pout.write(text)
    #создаем следующую часть сообщения
    subpart = mw.nextpart()
    #добавляем заголовок
    subpart.addheader('Content-transfer-encoding', 'base64')
    #определяем тип как application/octet-stream
    pout = subpart.startbody("application/octet-stream", [("name", name)])
    #открываем загружаемый файл и кодируем его в base64,
    #результат записываем в pout
    infile = open(src_file, "rb")
    mimetools.encode(infile,pout,'base64')
    infile.close()
    #завершаем многокомпонентное сообщение
    mw.lastpart()
    outfile.close()
 
    #далее посылаем стандартное сообщение с помощью класса SMTP,
    #тело сообщения читаем из сформированного ранее файла.
    f = open(tmp_file, 'rb')
    msg = f.read()
    f.close()
    mail=smtplib.SMTP("smtpserver.ru")
    out = StringIO.StringIO()
    out.write( "Subject: %sn" % subj )
    out.write( "From: %sn" % 'Intersoft Web Server  ' )
    out.write( "MIME-Version: 1.0\n" )
    out.write( msg )
    mail.sendmail( 'market@iso.ru', address, out.getvalue() )
    out.close()
    #удаляем временные файлы
    os.unlink(src_file)
    os.unlink(tmp_file)


А вот пример использования этой функции:


send_attach(msg, form['attach'].value, form['attach'].filename.split('')[-1], 'Attachment', 'market@iso.ru')


Проверка корректности форм

Большинство CGI-скриптов работают с данными, полученными из форм. Для успешного использования этих данных необходимо проверить их корректность. В простейшем случае, это - просто проверка существования заполненного поля:


form = cgi.FieldStorage()
if form.has_key('keyname'):
    #действия с данными

В более сложных случаях, таких как проверка корректности введенного e-mail адреса требуется использование модуля re для сопоставления полученных данных с регулярными выражениями. Например, регулярное выражение [-_w0-9]+@[-_w0-9]+.w+ определяет e-mail адрес следующим образом: [-_w0-9] обозначает любую букву, цифру или знак "-" и "_", + - одно или более повторений предыдущего выражения, @ - "собака", . - точка, w+ - не менее одной буквы. Таким образом все адреса вида name@domain1.domain2 попадают под это определение.


Административный интерфейс

Для облегчения сопровождения сайта был разработан административный интерфейс - набор скриптов для контроля за контентом. Он состоит из следующих частей:

  1. Главный административный скрипт; скрипт позволяет:
    • добавить новость, событие, статью в журнал, создать новую тему для журнала и новый журнал со статьей;
    • редактировать (и удалять) новости, события, статьи журнала, темы журнала, атрибуты пользователей, комментарии к статьям.
  2. Скрипт управления конференциями; скрипт обеспечивает просмотр и удаление сообщений, добавление новых конференций.
  3. Скрипт выгрузки данных; скрипт отвечает за экспорт данных из таблиц в формат CSV.

Все скрипты достаточно жестко привязаны к структуре сайта, поэтому нет смысла подробно разбирать их. Хочу затронуть только один момент: удобно сделать отдельную таблицу для размещения информации о полях таблиц, используемых скриптами сайта. Это позволит писать универсальные функции администрирования для разных таблиц. Например, функция, реализующая вывод списка записей на нашем сайте построена следующим образом: на входе она получает имя таблицы, по нему читается информация о таблице из служебной таблицы (названия полей, выводить ли поле в списке и т.д.) и на основании полученной информации выводится список записей. Так как список, состоящий из всех полей, очень громоздкий (статья в журнале может занимать несколько страниц), в списке выводятся только те поля, для которых в служебной таблице стоит флаг вывода в списке. Благодаря этому добавление новых таблиц в базу требует лишь записи информации о них в служебную таблицу.

 

 
              
   

Комментарии: Пишите мне Rambler's Top100 Copyright ©
Хостинг от uCoz