Zope – The Object Publishing Environment, Сервери, Інтернет-технології, статті

Олег Бройтмані Російськомовна Група користувачів Python і Zope.
При використанні матеріалів посилання на джерело обов'язкове.

ПРЕАМБУЛА – труднощі перекладу в Zope

По-перше, сама назва. Формально воно звучить як Z Object і так далі. Якщо написати The Object – воно має бути Tope, але це означає "пиячити". Так що не у одних росіян з цим словом проблеми.

По-друге, транскрипція або транслітерація – Зоп? Зопе? в Зопе? Останній варіант у деяких народжує неналежні думки і асоціації:) Я вважаю за краще писати Zope і вимовляти Зоп. Він, зрештою, сервер!

Введення в Zope

Zope – Це об'єктно-орієнтована платформа, сервер додатків, призначений для створення динамічних web-додатків та інтерактивних сайтів.

У виразу "об'єктно-орієнтована" тут декілька сторін. По-перше, Zope написаний на мові Python, Об'єктно-орієнтованої мови з множинним спадкуванням.

По-друге, Zope побудований довкола ідеї "публікації об'єктів" – URL, до якого звертається браузер, є посиланням на об'єкт (екземпляр класу), що викликається на виконання.

По-третє, самі об'єкти (серіалізовать екземпляри класів) зберігаються в об'єктно-орієнтованої базі даних ZODB.

Надалі я буду продовжувати вживати вираз "об'єктно-орієнтована" досить часто, не тому що це модне слово, а тому що невід'ємна властивість Zope.

Ще одне невід'ємне властивість – модульність. Zope – це не цілісний шматок софта, а багатий набір модулів, званих компонентами. "Компонент" – ще одне слово, яке я буду часто вживати.

Інші модні слова, типу XML, я буду вживати рідше. Це не означає, що Zope не працює з XML – працює ще й як, – просто до мого введенню це не має відношення, а я намагаюся "не вживати слова тільки за те, що вони красиві і довгі "(C) Керролл, переклад Демуровой).

Ще кілька модних слів, що мають відношення до справи: free software, open source, 64-біт (на відповідних ОС), многоплатформенность і переносимість (Zope написаний на мові портабельная Пітон і працює у всіх Юнікси і в Windows; основний формат бази даних ZODB – файл Data.fs – повністю незалежний від платформи і ОС), масштабованість і розподіленість (за допомогою компонента ZEO, про що пізніше).

Чому Zope

Протоколи WWW (HTTP, CGI і т.д.) часто неадекватні завданням і можуть робити публікацію динамічних даних невиправдано складною. Їх низький рівень недостатній для безпосереднього створення багатьох класів web-додатків на їх основі.

Zope створює об'єктно-орієнтовану оболонку навколо цих низькорівневих засобів. З його допомогою рішення задачі відбувається звичайним шляхом – програміст пише набір ієрархій класів, що є абстракцією предметної області, а Zope бере на себе працю з надання доступу до екземплярів цих класів.

Користувачі Zope

C Zope працюють наступні категорії користувачів:

Це, звичайно, не обов'язково різні люди – це ролі. На маленькому сайті ці ролі може виконувати одна людина. Для великих сайтів Zope надає механізми делегування повноважень адміністраторам ділянок сайтів, верстальщикам, редакторам.

Компоненти Zope

Zope Core

В "серце" Zope знаходиться ORB (object request broker), а також механізми, що забезпечують пошук (ZCatalog), безпеку, колективну роботу і розділення інформації. Zope має web-інтерфейс для програмування та адміністрування.

ZServer

Багатопотоковий ZServer надає гнучкий механізм зв'язку, підтримуючи протоколи HTTP, FTP, XML-RPC, FastCGI і PersistentCGI. Zope може бути запущений з ZServer, причому можна використовувати ZServer спільно з уже існуючим WWW сервером, або ж Zope можна запустити з-під існуючого WWW сервера в режимі PCGI (однопотоковий сервер PersistentCGI).

Object Database (ZODB)

Об'єктно-орієнтована база Zope зберігає об'єкти (саме об'єкти в сенсі Python, тобто серіалізовать екземпляри класів); сама ZODB написана об'єктно-орієнтовано, тобто як набір дерев класів. У ZODB можна довільно міняти клас StorageManager – сховище. Стандартне сховище FileStorage зберігає дані у файлі Data.fs, але можна використовувати альтернативні класи – SQLStorage або BerkeleyStorage. ZODB підтримує атомарні операції (транзакції), необмежений undo (тільки з відповідним сховищем, наприклад, FileStorage або InterbaseStorage підтримують Версії і відкат, а решта сховища – Ні), приватні Версії, і масштабується до гігабайтів даних, що зберігаються. Окремий механізм ZEO (Zope Enterprise Option) дозволяє підвищити надійність і масштабованість шляхом кластеризації. Власне, ядром ZEO є ще одне сховище ServerStorage, яке звертається не до локального Data.fs, а до віддаленого серверу; другим компонентом ZEO є якраз сервер.

Document Template Markup Language (DTML)

За цією назвою ховається багатий механізм інтерпретації (рендеринга) шаблонів. Прості сайти можна створювати, взагалі не звертаючись до Пітона – на одному DTML (природно, користуючись, вже готовими компонентами Zope).

Інтеграція з реляційними СУБД

Zope має рівень абстракції ZSQL, що дозволяє легко інтегрувати систему з SQL, будь то PostgreSQL, Oracle, MySQL або ODBC.

Продукти Zope

Продукти – компоненти, написані програмістом на Пітоні – дозволяють доповнювати Zope новими типами об'єктів. Наприклад, компонент (назвемо його умовно Poll) для створення на сайті голосувань. Після того, як програміст напише відповідні класи, webмастер розставить екземпляри цих класів на сайті і створить кожному з екземпляру дизайн; редактор сайту наповнить їх вмістом (питання і список відповідей для кожного екземпляра); і відвідувачі сайту можуть починати голосувати!

ZClasses

Z-класи – це механізм програмування "мишкою", програмування без програмування. Z-класи не вимагають знання програмування, і в той же час дозволяють створювати нові типи даних (компоненти) через web. Створені програмістом Z-класи легко поширюються і встановлюються.

Що дає Zope

Програмісту:

web-майстрові:

адміністратора:

Програмування для Zope

Програмування для цієї складної і гнучкої платформи здійснюється різними механізмами і на різних мовах.

1. Програмування на DTML. Це не стільки програмування, скільки верстка, робота webмастера. З DTML доступне велике число функцій і об'єктів Пітона і Зоп, за винятком тих, які приховані з міркувань безпеки. DTML призначений переважно для презентації, а не для маніпуляції даними.

2. PythonMethods. Код пишеться на Пітоні і вводиться через web-інтерфейс Zope. На цей код поширюються ті ж обмеження безпеки, що і на DTML. Зазвичай PythonMethod – одна або кілька простих функцій.

3. ExternalMethods. Це теж код на Пітоні, і теж зазвичай кілька пов'язаних функцій. На цей код не поширюються обмеження безпеки (в тому сенсі, що цей код має доступ до всіх функцій, типам і класам Пітона і Zope, але сам цей код можна захистити від додавання або використання засобами безпеки Zope), і ставиться він у файлову частину Zope руками адміністратора хоста, а потім додається в ієрархію об'єктів Zope через web-інтерфейс.

4. Компоненти. Вони пишуться на Пітоні за допомогою Product API. Компонент – це клас або набір дерев класів. Ніяких обмежень по безпеці (у вже вказаному для ExternalMethods сенсі; використання ж методів компонента може бути захищене спільними зусиллями програміста і адміністратора сайту). Код цей ставиться у файлову систему адміністратором хоста, і Zope доводиться перезапускати. Після цього в списку Продуктів з'являється новий Продукт (а то й не один, якщо програміст або адміністратор хоста разом ініціалізував декілька Продуктів або в одному Продукті реєструє декілька Виробників (Конструкторів)), екземпляри якого можна створювати в будь-якому місці ієрархії об'єктів.

5. ZClass. "Програмування мишкою". Творець Z-класу розписує, які в об'єкту є атрибути, і створює на DTML способи редагування і показу екземплярів класу. Все "програмування" йде через web-інтерфейс Zope. Z-Клас додається в список Продуктів, і можна створювати його екземпляри. При зміні програмістом Z-класу всі екземпляри міняються автоматично (тобто екземпляри містять не копію коду, а посилання на клас). Z-класи можна успадковувати від багатого базового набору класів Zope, можна від інших Z-класів, і програміст може створити Компонент, що включає класи, від яких можна успадковувати Z-класи.

Публікація об'єктів

Zope публікує Пітоновскіе об'єкти (екземпляри класів). Для цього в Zope є компонент ZPublisher – брокер об'єктних запитів. Отримавши запит (від ZServer'а, який у свою чергу отримує запит з зовнішнього світу з одного з підтримуваних протоколів), він:

Без допомоги з боку програми ZPublisher, звичайно, не може здійснити відповідні перетворення типів, тому автор може вказати, в якому вигляді він хоче отримати дані. Ось приклад форми для заповнення, до вказівок, зашитими в імена полів:

<FORM name="formA" action="myObject" method="POST">
   <input type="text" name="age:int" size="2">
   <input type="checkbox" name="category:int:list">K1
   <input type="checkbox" name="category:int:list">K2
<input type="submit" name="manage_setAge:method" value="Установіть">
<input type="submit" name="manage_delete:method" value="Удаліть">
</FORM>

Після заповнення форми у браузері і натиснення однієї з кнопок ZPublisher перетворює введені дані. Мінлива age перетворюється в ціле, checkbox'и – в список цілих, і викличеться один з методів об'єкту myObject в залежності від натиснутої кнопки.

Перевірка, природно, здійснюється на стороні сервера, в Zope. Якщо змінна age не перетворює в ціле, виникне помилка. Її може обробити публікується об'єкт, а ні – Zope видасть користувачеві HTML з текстом про помилку. Для перевірки на стороні клієнта можна використовувати JavaScript. Зогнуті імена злегка заважають доступу з JS, але це не смертельно: element = document.forms ["formA"]. Elements ["age: int"]

Як саме викличеться метод, залежить від його (методу) сигнатури (в Пітоні вся інформація про код доступна під час виконання). Наприклад, якщо myObject – примірник ось такого класу:

   class AgeManager:
      def view(self, age=None):
         if age is None:
            age = self.age
return "Вік: <b>% d </ b>"% age
      def manage_setAge(self, age):
         self.age = age
      def manage_delete(self, category):
for c in category: self.delete (c) # self.delete не показаний

то метод manage_setAge викличеться з цілим age, або manage_delete – зі списком натиснутих CheckBox'ов. Решта змінні форми можна отримати з загального простору імен, доступного через self.

Публікація через метод GET і того простіше: на запит http://www.my.server/root/subobject/sub2/myObject/view?age:int=12

ZPublisher обходить ієрархію об'єктів (траверс з урахуванням механізму acquisition, про що пізніше) і публікує myObject – у об'єкту викликається метод view з цілочисельним параметром.

ACQUISITION – запозичення замість наслідування

Acquisition – це механізм запиту значень змінних з поточного контексту. Переведемо це слово як "запозичення" атрибутів; щодо адекватний переклад.

Запозичення значення атрибута відбувається з контексту об'єкта. Контекстів буває два – статичний, що виникає в момент створення об'єкту, і динамічний, що виникає під час виклику методу об'єкта на виконання. Звідки саме відбувається запозичення – цим управляє програміст при створенні або використанні компонента.

Контекст – це стек, в якому відбувається пошук атрибута. Наприклад, якщо є контекст [object, sub2, myObject] (на вершині знаходиться myObject), і myObject запросив значення атрибута color, то пошук буде відбуватися в глибину стека. Спочатку атрибут з таким ім'ям буде шукатися в myObject, якщо його там немає – пошук перейде до sub2, потім до object.

Статичний контекст – це шлях від кореня ZODB (ZODB, не сайту!) До об'єкта в ієрархії об'єктів. Динамічний контекст – це шлях (стек), що виникає під час обходу ієрархії об'єктів компонентом ZPublisher при зверненні до об'єкта через URL.

Наприклад, якщо є шлях / root / object / subobject / myObject, то це і є статичний контекст (точніше, контекстом є стек об'єктів [root, object, subobject, myObject]).

Динамічний контекст залежить від URL. Якщо відбулося звернення до адреси http://www.server/root/object/subobject/myObject, то в цьому випадку динамічний контекст збігається зі статичним. Але при зверненні до http://www.server/root/english/object/subobject/myObject (де english – папка в об'єкті root) контекст буде інший – в стек додасться об'єкт english. Щоб зрозуміти, на яке саме місце englsih додасться, треба детально розглянути процес траверса. ZPublisher сам теж використовує механізм acquisition, так що в цілому розбір адреси http://www.server/root/english/object/subobject/myObject відбувається наступним чином.

Отримавши (від ZServer'а) шлях / root / english / object / subobject / myObject, ZPublisher починає обходити окремі частини шляху, будуючи по ходу стек. Спочатку стек порожній, потім до нього додається root (пошук починається від кореня ZODB, та перевіряється, що об'єкт з таким ім'ям є в корені), потім ZPublisher виявляє english і запитує його (з урахуванням запозичення); об'єкт виявляється в / root і потрапляє в стек, потім йде object, який запозичується не з english, а з / root, потім нормальним шляхом ідуть subobject і myObject. В даному випадку стек просто співпав з URL. Але якби в english був свій object, він би запозичувався б звідти, а не з / root. І якби в цьому object не було subobject, то subobject знову запозичувався б із / root (їли він там є). У результаті ми мали б контекст (стек) [/ root, / root / english, / Root / english / object, / root / subobject, myObject]. І якби myObject запросив атрибут language, відсутній в / root / subobject, він отримав би його з / root / english / object, а не з / root / object!

Таким чином, змінюючи порядок компонент в URL, програміст може абсолютно міняти вигляд і зміст сайту, не дублюючи при цьому величезні шматки коду або тексту. Треба лише виробити правильну факторизацию – Розбити код та оформлення на невеликі об'єкти, і будувати контекст (він ще називається acquisition path – маршрут запозичення значень атрибутів).

Розглянемо детальний приклад. Два основних об'єкта Zope – це класи DTML Document і DTML Method, включені в дистрибутив Zope. Вони призначені для різного типу використання. DTML Document зберігає зміст, текст, його шлях – запозичення із статичного контексту. DTML Method призначений для активних дій, він запозичує значення з динамічного контексту. Ще є клас Folder – папка. У ній зберігаються інші об'єкти.

Нехай, скажімо, Документ my.html починається зі стандартного заголовка, і закінчується стандартним підвалом. Мовою DTML це виражається як <dtml-var standard_html_header> і <dtml-var standard_html_footer>. Розмістимо ці об'єкти на невеликому абстрактному (тобто існуючим тільки в наших головах) сайті. Нехай є корінь (корінь в ZODB є завжди), у ньому кілька папок, скажімо, Razdel1 і Razdel2, 2 Методу – Header і footer, і в Razdel2 – наш my.html.

/
   standard_html_header
   standard_html_footer
   Razdel1
   Razdel2
      my.html

Отже, браузер звертається до http://www.server/Razdel2/my.html. ZPublsiher будує контекст [/, / Razdel2, / Razdel2/my.html] і викликає рендеринг my.html. Той починає рендерится, і на самому початку зустрічає <dtml-var standard_html_header>. Запитується значення заголовка. У my.html такого об'єкта немає, в Razdel2 немає, пошук переходить в корінь – там такий є. Він виконується (рендерится), потім виконання повертається в my.html, потім footer – все.

Візьмемо і додамо в Razdel2 інший header:

/
   standard_html_header
   standard_html_footer
   Razdel1
   Razdel2
      standard_html_header
      my.html

І знову звернемося до http://www.server/Razdel2/my.html. Тепер my.html запозичить інший header, і виглядати буде по-іншому!

Додамо в корінь новий розділ, з іншими header / footer:

/
   standard_html_header
   standard_html_footer
   Razdel1
   Razdel2
      standard_html_header
      my.html
   NewLook
      standard_html_header
      standard_html_header

І звернемося до http://www.server/Razdel2/NewLook/my.html. Чи буде my.html використовувати header з NewLook? Ні! my.html – DTML Document, і завжди використовує статичний контекст. Його acquisition path завжди [/, /Razdel2, /Razdel2/my.html].

Додамо в Razdel1 об'єкт DTML Method index.html

/
   standard_html_header
   standard_html_footer
   Razdel1
      index.html
   Razdel2
      standard_html_header
      my.html
   NewLook
      standard_html_header
      standard_html_header

І звернемося до http://www.server/Razdel1/index.html. Оскільки це Метод, то буде використаний динамічний контекст, але в даному випадку він збігається зі статичним. А ось при зверненні до http://www.server/Razdel1/NewLook/index.html динамічний контекст буде іншою, і index.html запозичить атрибути з NewLook – і буде виглядати по іншому!

Змінимо сайт востаннє. Всі видалимо,

/
   standard_html_header
   standard_html_footer
   Razdel1
      index.html
   Razdel2
      my.html

і відредагуємо header / footer, так щоб вони включали на сайті, скажімо, ліву колонку. Назвемо її left-column, і створимо її в корені і в розділах:

/
   standard_html_header
   standard_html_footer
   left-column
   Razdel1
      index.html
      left-column
   Razdel2
      my.html
      left-column

Тепер при виклику http://www.server/Razdel1/index.html буде показуватися одна колонка, http://www.server/Razdel2/my.html – інша. А header при цьому один на всіх! Як header знає, яку колонку використовувати? Дуже просто – він бере участь в пошуку по acquisition path, за контекстом (статичному або динамічному в залежності від того, звідки його викликали), не більше того.

Ці різні left-column навіть не зобов'язані навіть бути екземплярами одного класу. У корені це може бути DTML Method, а в Razdel2 – ZNavigator. Header'у все одно, кого рендери, він викликає left-column, нічого не знаючи про його тип і пристрої (знову об'єктно-орієнтоване програмування).

Ще один приклад, ближче до реального життя з Zope, але менш докладний. Попереднє зауваження: коли URL посилається не на метод об'єкта, а на сам об'єкт, у нього викликається метод index_html.

Створимо маленький сайт. В корінь помістимо DTML Method index_html простого змісту:

   <dtml-var standard_html_header>
   <dtml-var content>
   <dtml-var standard_html_footer>

і DTML Document content, який зберігає власне зміст розділу, взагалі без заголовка / підвалу.

/
   standard_html_header
   standard_html_footer
   index_html
   content
   Razdel1
      content
   Razdel2
      content

Звернемося до кореня сайту: http://www.server/. Коренева папка викличе свій index_html, який інтерпретується, довантажити відповідні заголовок і підвал. Нічого особливого.

Тепер звернемося до одного з розділів: http://www.server/Razdel1/ Цей папка, тому вона викличе свій index_html … Але в Razdel1 немає свого index_html. Він запозичується з кореня! Ну, і оскільки він DTML Method, то він сам запозичує атрибути з динамічного контексту. Header / footer візьмуться з кореня, а content з Razdel1.

Третій, і останній приклад зовсім коротко. У папці db лежать dtml-методи (нехай dtml-методи будуть називатися db / view, db / insert, db / update), і sql-методи, які параметризовані (ім'я таблиці, імена стовпців).

Далі, всередині цієї папки робиться папка, наприклад users. У її атрибути прописуються конкретні параметри для методів (ім'я таблиці, імена стовпців).

За зверненням db / users / view – отримуємо готову сторінку з вмістом таблиці. Метод view (так само як і insert і update) успадкований з db, але запозичує атрибути з users.

Метод db / users / insert (успадкований з db) прочитає з властивостей папки db / users назва таблиці, назви полів, і сконструює форму, щоб додати записи. Те ж відбуватиметься з іншими папками, та їх властивостями. У ході розвитку проекту, точно так-само як і для випадку ГО програмування, додавання нових методів в "базовий об'єкт" db (наприклад потрібно буде зробити пошук – db / search) автоматично розширить функціональність "нащадків" db / users, db / something …

SECURITY – механізми безпеки в Zope

Zope надає програмістам і адміністраторам прості, і в той же час могутні і гнучкі механізми управління безпекою. Безпека в Zope стоїть на трьох китах, трьох базових поняттях – користувач, роль, і вид доступу.

Вид, або тип доступу визначає програміст при створенні компонента. Кожному класу в компоненті визначається повноваження "Add" ("Додати екземпляр класу в дерево об'єктів"), кожному методу класу можна визначити свої власні повноваження, які визначать, кому і який вид доступу надано до цього методу класу. Наприклад, методу index_html (який викликається при зверненні до об'єкта, а не до конкретного методу) зазвичай дається вид доступу View. Але це справа програміста, як назвати свої повноваження, і які методи якими повноваженнями захистити. Зазвичай методи об'єкта об'єднуються в групи, що мають один сервіс. Наприклад, клас НовостеваяЛента може мати сервіси (групи методів) "показ новин", "додавання новин", "редагування новин", "видалення новин", "додавання / редагування / видалення рубрик ". І кожен з сервісів можна захистити (давши йому окремий вид доступу) – з точністю до одного методу. Для більш тонкого управління, вже всередині методу, програміст може запросити SecurityManager – "Чи має поточний користувач права на створення DTML Методів в папці Razdel?"

Ролі створює адміністратор сайту через менеджерський web-інтерфейс Zope. Поняття ролі поширюється не на весь сайт, не на ZODB, а на частину дерева. Адміністратор створює роль в іншому місці, і далі завдяки механізму acquisition ця роль поширюється вниз по піддереві.

Zope, поставлена ​​з дистрибутива, має 3 ролі, визначені в корені ZODB – Anonymous, Owner і Manager. Manager – це такий всесильний адміністратор, аналог рута. Owner – власник тих ресурсів, які він створив. Анонімний користувач – просто відвідувач сайту; йому спочатку доступні типи доступу: Access content, View, Use SQL Methods (це для того, щоб дозволити викликати SQL Методи з DTML Методів) і Search ZCatalog.

Адміністратор сайту в подальшому може створювати нові ролі, як в корені, якщо у самого адміністратора є права на редагування кореня, так і в будь-яких піддерева, на які у адміністратора є права.

Ці 2 механізму – категорії доступу та ролі – абсолютно ортогональні, і в web-інтерфейсі утворюють табличку з сотень CheckBox'ов – якій ролі які категорії доступу.

Адміністратор сайту може, наприклад, завести ролі Editor і ChiefEditor, і дати ролі Editor права на редагування DTML Document'ов, а ролі ChiefEditor – права на редагування DTML Method'ов, картинок, і на створення папок. Давши ролі SubAdmin права на адміністрування безпеки в піддереві сайту, адміністратор ефективно делегує повноваження.

Користувачі (тобто записи про користувачів) – це об'єкти (як і все інше), екземпляри класу User. Спочатку Zope ставиться з компонентом UserFolder, який зберігає ці об'єкти в ZODB, і може отримувати і перевіряти username / пароль лише за схемою Basic Authentication. Але компонентна технологія і тут дає свої переваги. Вже доступні компоненти:

Всі перераховані компоненти вміють як Basic Auth, так і куки. На сайті можна розставити скільки завгодно примірників різних компонент, і таким чином авторізовиваться одну частину сайту в домені NT, а іншу – З SQL. На жаль, поставити в одну папку 2 різних UserFolder можна.

Тип доступу для користувача перевіряється не безпосередньо, а через ролі. Запис про користувача (об'єкт класу User), крім логіну та паролю, зберігає список його ролей, і список доменів і / або IP, з яких користувачеві дозволено працювати.

Знову-таки, завдяки механізму запозичення, запис користувача доступна і перевіряється скрізь в піддереві, на вершині якого цей запис внесена.

Ще один механізм потрібен, якщо якесь привілейоване дію треба дозволити зробити користувачеві, що не володіє потрібними привілеями. Скажімо, дати на перегляд (але тільки на перегляд) Документ, доступний тільки Менеджеру. Тоді це конкретна дія описується (скажімо, на DTML пишеться Метод для перегляду), і цьому Методу дається Proxy Role під роль Manager. Повний аналог юніксового setuid, і як з усяким setuid, треба бути дуже уважним, щоб не настворювали дір в захисті.

Нарешті, останній механізм, Local Role, дозволяє дати певному користувачеві додаткові права (ролі) на конкретний об'єкт. Так-то ролі даються користувачеві там, де визначена запис користувача; Local Role дозволяє визначити додаткові ролі в контексті об'єкта, а не для користувача записи. Локальні ролі не запозичуються механізмом acquisition.

Недоліки Zope

Недоліки Zope в основному є продовженням переваг цієї платформи.

Деякі особливості мають окремі компоненти Zope.

Російськомовна група користувачів Python і Zope

У нас ще немає свого сайту, він у процесі створення, але група відрізняється активністю та інтересом до просування технології Zope. Ви можете підписатися на наш список розсилки, надіславши листа з тілом (з тілом, не з темою) subscribe python за адресою majordomo@list.glasnet.ru. Адміністратор списку – Євген Дворічанський; Хостинг списку – Росія-Он-Лайн.

 

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*